How to display all data with nulls using Pivot [duplicate] - oracle

please help me solve this problem:
You are given a table, containing two columns:
column is one of the followings:
Doctor
Professor
Singer
Actor
Write a query to output the names underneath the corresponding occ. in the following format:
+--------+-----------+--------+------+
| Doctor | Professor | Singer | Actor|
+--------+-----------+--------+------+
Names must be listed in alphabetically sorted order.
Sample Input
Name Occupation
Meera Singer
Ashely Professor
Ketty Professor
Christeen Professor
Jane Actor
Jenny Doctor
Priya Singer
Sample Output
Jenny Ashley Meera Jane
Samantha Christeen Priya Julia
NULL Ketty NULL Maria
Note
Print "NULL" when there are no more names corresponding to an occupation.
I tried using :
SELECT *
FROM
(
SELECT [Name], [Occupation]
FROM occupations
) AS source
PIVOT
(
max([Name])
FOR [occupation] IN ([Doctor], [Professor], [Singer], [Actor])
) as pvt;
which gives the following output:
Priya Priyanka Kristeen Samantha
How to fix it ?

You just need to give each name a row number based on their occupation and order alphabetically.. then include that row number in your pivot query.
CREATE TABLE Occupations (
NAME VARCHAR(MAX),
Occupation VARCHAR(MAX)
)
INSERT INTO Occupations
VALUES
('Samantha','Doctor'),
('Julia','Actor'),
('Maria','Actor'),
('Meera','Singer'),
('Ashley','Professor'),
('Ketty','Professor'),
('Christeen','Professor'),
('Jane','Actor'),
('Jenny','Doctor'),
('Priya','Singer');
SELECT
[Doctor],
[Professor],
[Singer],
[Actor]
FROM
(SELECT
ROW_NUMBER() OVER (PARTITION BY Occupation ORDER BY Name) rn,
[Name],
[Occupation]
FROM
Occupations
) AS source
PIVOT
(MAX(Name) FOR [occupation] IN ([Doctor],[Professor],[Singer],[Actor])) as pvt
ORDER BY rn
DROP TABLE Occupations
-- Edit: We need to enclose the subquery after PIVOT within parenthesis "()" to make it work on SQL Server.

I tried this in Oracle, seemed easier to comprehend:
SELECT min(Doctor), min(Professor), min(Singer), min(Actor)
FROM
( Select
ROW_NUMBER() OVER (PARTITION BY Occupation order by Name) rn,
CASE
WHEN Occupation = 'Doctor' then Name
end as Doctor,
CASE
WHEN Occupation = 'Professor' then Name
end as Professor,
CASE
WHEN Occupation = 'Singer' then Name
end as Singer,
CASE
WHEN Occupation = 'Actor' then Name
end as Actor
from OCCUPATIONS
order by Name) a
group by rn
order by rn;

SELECT [Doctor], [Professor], [Singer], [Actor] FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY Occupation ORDER BY Name) ROW_NO,
ISNULL(NULL,Name) as Name, Occupation
FROM Occupations
) AS t
PIVOT(
MAX(Name)
FOR Occupation IN (
[Doctor],
[Professor],
[Singer],
[Actor]
)
) AS pivot_table
ORDER BY ROW_NO;

Here is the MYSQL version of the answer. Little difficult because of no presence of FULL OUTER JOIN in HACKERRANK.com
SET #r1=0, #r2 = 0, #r3 = 0, #r4 = 0;
SELECT t1.name, t2.name, t3.name, t4.name
FROM
(SELECT
(#r1:=#r1 + 1) AS num1,
Name
FROM
Occupations
WHERE Occupation = 'Doctor'
ORDER BY Name) as t1
RIGHT JOIN
(SELECT
(#r2:=#r2 + 1) AS num2,
Name
FROM
Occupations
WHERE Occupation = 'Professor'
ORDER BY Name) as t2
ON t1.num1 = t2.num2
LEFT JOIN
(SELECT
(#r3:=#r3 + 1) AS num3,
Name
FROM
Occupations
WHERE Occupation = 'Singer'
ORDER BY Name) as t3
ON t2.num2 = t3.num3
LEFT JOIN
(SELECT
(#r4:=#r4 + 1) AS num4,
Name
FROM
Occupations
WHERE Occupation = 'Actor'
ORDER BY Name) as t4
ON t2.num2 = t4.num4

SET #r1=0, #r2=0, #r3 =0, #r4=0;
SELECT MIN(Doctor), MIN(Professor), MIN(Singer), MIN(Actor) FROM
(SELECT CASE Occupation WHEN 'Doctor' THEN #r1:=#r1+1
WHEN 'Professor' THEN #r2:=#r2+1
WHEN 'Singer' THEN #r3:=#r3+1
WHEN 'Actor' THEN #r4:=#r4+1 END
AS RowLine,
CASE WHEN Occupation = 'Doctor' THEN Name END AS Doctor,
CASE WHEN Occupation = 'Professor' THEN Name END AS Professor,
CASE WHEN Occupation = 'Singer' THEN Name END AS Singer,
CASE WHEN Occupation = 'Actor' THEN Name END AS Actor
FROM OCCUPATIONS ORDER BY Name) AS t
GROUP BY RowLine;

Related

Write a query to find the name of the student(s) who has scored maximum mark in Software Engineering. Sort the result based on name

Write a query to find the name of the student(s) who has scored maximum mark in Software Engineering. Sort the result based on name.
This is what I tried.
select s.student_name
from student s,mark m,subject su
where s.student_id=m.student_id and su.subject_id=m.subject_id and max(m.value)in
(select value from mark where lower(su.subject_name)='software engineering')
order by s.student_name;
I am not getting the proper result.
Apart from the fact that you're using outdated implicit comma syntax for joins, you are also combining columns of the tables in the wrong way in the sub query.
subject_name is a column of subject which has nothing to do with the student's relation to marks. So, mark may be joined separately with subject while determining the student_ids with highest mark. We can then obtain the name of the student using those student_ids
So, In Oracle 12c and above, you could do
SELECT s.student_name
FROM student s
WHERE s.student_id IN ( SELECT m.student_id
FROM mark m JOIN subject su
ON su.subject_id = m.subject_id
WHERE lower(su.subject_name) = 'software engineering'
ORDER BY m.value DESC
FETCH FIRST 1 ROWS WITH TIES ) order by 1;
For previous versions, you may use dense_rank or rank
SELECT s.student_name
FROM student s
WHERE s.student_id IN ( SELECT student_id
FROM ( SELECT m.*,DENSE_RANK() OVER(
ORDER BY m.value DESC
) AS rnk
FROM mark m JOIN subject su
ON su.subject_id = m.subject_id
WHERE lower(su.subject_name) = 'software engineering'
) WHERE rnk = 1
) order by 1;
you need to add the max function in the subquery instead of the main query.
select s.student_name
from student s, mark m, subject su
where s.student_id = m.student_id
and su.subject_id = m.subject_id
and m.value in
(select max(value)
from mark
where lower(su.subject_name) = 'software engineering')
order by s.student_name;
You can try this code. It must work perfectly :
select s.student_name
from student s
join mark m
on s.student_id=m.student_id
join subject s1
on s1.subject_id=m.subject_id
where m.value=(select max(m1.value) from mark m1
join subject s2
on s2.subject_id=m1.subject_id
where lower(s2.subject_name)='software engineering'
group by s2.subject_id)
group by s.student_name
order by s.student_name;
select student_name
from student join mark using(student_id) join subject using(subject_id)
where subject_name = 'Software Engineering'
and value = (select max(value)
from mark join subject using (subject_id)
where subject_name = 'Software Engineering')
order by student_name;
This is very self-explanatory solution.
select student_name
from student s join mark m on s.student_id=m.student_id
join subject sub on m.subject_id=sub.subject_id
where lower(sub.subject_name)='software engineering' and
m.value = (select max(m1.value)
from mark m1 join subject sub1 on m1.subject_id=sub1.subject_id
where lower(sub1.subject_name)='software engineering')
order by student_name;
select student_name
from stundent
where student_id in(
select student_id
from mark
where value=(select max(value) from mark
where subject_id=(
select subject_id
from subject
where lower(subject_name)='software engineering'
)
)
)
)
order by student_name;
select student_name
from student
where student_id in
(select student_id
from subject join mark using(subject_id)
where value in
(select max(value)
from mark
group by student_id
having initcap(subject_name) = 'Software Engineering'))
order by student_name;

Missing right parenthesis error in query oracle

Tables needed -
Habits(conditionId, name)
Patient(patientId, name, gender, DoB, address, state,postcode, homePhone, businessPhone, maritalStatus, occupation, duration,unit, race, registrationDate , GPNo, NaturopathNo)
PatientMetabolic (functionNo, patientId, score)
The Question Is -
Question - Display the details of the patient (i.e. Name, Gender, Address, Postcode, DOB) who smokes and has the highest (most severe) total of metabolic functions.
(conditionid for smoke is H1 in Habit table)
(metabolic function are in patientbetabolic table functionNo)
(To find the highest most severe total of metabolic function we need to create a sum of score which tells who has the most metabolic functions)
My query -
SELECT *
FROM patient
where patientid IN (SELECT patientid,SUM(score) as totalscore
from PATIENTMETABOLIC
where patientid IN (SELECT patientid
from patienthabit
where conditionid = 'H1')
group by patientid
order by totalscore desc);
Error:
ORA-00907: missing right parenthesis
An alternative way to do this is by using joins.
select * from (select p.patientid,p.name,sum(pm.score) as total from patient p join patienthabit ph on p.patientid = ph.patientid
and ph.conditionid = 'H1' Left join patientmetabolic pm
on p.patientid = pm.patientid group by p.patientid,p.name order by 3 desc) where ROWNUM = 1;
Try this:
SELECT *
FROM PATIENT
WHERE PATIENTID = (SELECT PATIENTID
FROM (SELECT patientid, SUM(score)
from PATIENTMETABOLIC
where patientid IN (SELECT patientid
from patienthabit
where conditionid = 'H1')
group by patientid
order by SUM(score) desc)
WHERE ROWNUM = 1);
SQLFiddle here
Share and enjoy.
Since the first inner query returns patientid and totalscore, you can't use it as a list against IN operator.
The result of this query might be similar to that of your query:
SELECT p.*
FROM patient p JOIN
patienthabit ph ON p.patientid=ph.patientid
WHERE ph.conditionid='H1'

identify duplicates as well as the matched unique record in oracle

hi i am running the follwoing query to identify the duplicate records.
SELECT *
FROM unique2 P WHERE EXISTS(SELECT 1 FROM unique2 C
WHERE ( (C.surname) = (P.surname))
AND ( (C.postcode) = (P.postcode))
AND ((( (C.forename) IS NULL OR (P.forename) IS NULL)
AND (C.initials) = (P.initials))
OR (C.forename) = (P.forename))
AND ( (C.sex) = (P.sex)
OR (C.title) = (P.title))
AND (( (C.address1))=( (P.address1))
OR ( (C.address1))=( (P.address2))
OR ( (C.address2))=( (P.address1))
OR instr(C.address1_notrim, P.address1_notrim) > 0
OR instr(P.address1_notrim, C.address1_notrim) > 0)
AND C.rowid < P.rowid);
But with this query i can't identify the unique record id which is matched to the duplicate records. Is there a way to identify the
duplicates as well as the unique record id(my table has unique key) to which those duplicates are matched?
select id
from promolog
where surname, postcode, dob in (
select surname, postcode,dob
from (
select surname, postcode, dob, count(1)
from promolog
group by surname,postcode,dob
having count(1) > 1
)
)
You can also do this with analytic functions:
select id, num_of_ids, first_id, surname, postcode, dob
from (
select id,
count(*) over (partition by surname, postcode, dob) as num_of_ids,
first_value(id)
over (partition by surname, postcode, dob order by id) as first_id,
surname,
postcode,
dob
from promolog
)
where num_of_ids > 1;
Based on your update, I think you can just do a self-join, which you can make as complicated as you like:
select dup.*, master.id as duplicate_of
from promolog dup
join promolog master
on master.surname = dup.surname
and master.postcode = dup.postcode
and master.dob = dup.dob
... and <address checks etc. > ...
and master.rowid < dup.rowid;
But maybe I'm still missing something. As the name suggests, exists is for testing the existence of a matching record; if you want to retrieve any of the data from the matched record then you'll need to join to it at some point.

vertical output

I have column name in the table:
select LASTNAME
FROM dbo.Employees
WHERE LASTNAME = 'Smith'
and output of the above query is
LASTNAME
Smith
I want output like
LASTNAME
S
m
i
t
h
With a little help of a numbers table.
SQL Server:
select substring(E.LASTNAME, N.N, 1) as LASTNAME
from Employees as E
inner join Numbers as N
on N.N between 1 and len(E.LASTNAME)
order by E.LASTNAME, N.N
Oracle:
select substr(E.LASTNAME, N.N, 1) as LASTNAME
from Employees E
inner join Numbers N
on N.N between 1 and length(E.LASTNAME)
order by E.LASTNAME, N.N;
SQL Fiddle
In SQL Server, if you don't have a table of numbers, then you can use CTE to generate the list:
;with cte (id, start, numb) as
(
select id, 1 start, len(lastname) numb
from employees
union all
select id, start + 1, numb
from cte
where start < numb
)
select c.id, substring(e.lastname, c.start, 1)
from employees e
inner join cte c
on c.start between 1 and len(e.lastname)
and c.id = e.id
order by e.id, e.lastname;
See SQL Fiddle With Demo
----- function for splitting
CREATE FUNCTION [dbo].[SPLIT_Test] (
#string VARCHAR(8000) )
RETURNS #table TABLE (strval VARCHAR(8000))
AS
BEGIN
IF LEN(#string)>=1
BEGIN
DECLARE #fulllen int=LEN(#string),#lastlen int=0
WHILE #fulllen>#lastlen
BEGIN
INSERT INTO #table
SELECT SUBSTRING(#string,1,1)
SET #string= RIGHT(#String, LEN(#String) - 1)
SET #lastlen=#lastlen+1
END
RETURN
END
RETURN
END
---- query
GO
DECLARE #name table(name varchar(500),row int IDENTITY(1,1))
INSERT INTO #name
select LASTNAME
FROM dbo.Employees
WHERE LASTNAME = 'Smith'
DECLARE #Finalname table(name varchar(50))
DECLARE #startrow int =(SELECT MAX(row) FROM #name)
,#endrow int =1
WHILE #startrow>=#endrow
BEGIN
INSERT INTO #Finalname
Select strval from [dbo].[SPLIT_test] ((SELECT name FROM #name where row=#endrow)) WHERE strval<>''-- removing empty spaces
SET #endrow=#endrow+1
END
SELECT * FROM #Finalname

Efficiently get min,max, and summary data

I have a table of accounts and a table of transactions. In a report I need to show the following for each account:
First Purchase Date,
First Purchase Amount,
Last Purchase Date,
Last Purchase Amount,
# of Purchases,
Total of All Purchases.
The transaction table looks like this:
TX_UID
Card_Number
Post_Date
TX_Type
TX_Amount
Currently the query I've inherited has a sub-query for each of these elements. It seems to me that there's got to be a more efficient way. I'm able to use a stored procedure for this and not a single query.
A sample of a query to get all transactions for a single account would be:
select * from tx_table where card_number = '12345' and TX_Type = 'Purchase'
Any ideas?
try this:
select tt1.post_date as first_purchase_date,
tt1.tx_amount as first_purchase_amount,
tt2.post_date as last_purchase_date,
tt2.tx_amount as last_purchase_amount,
tg.pc as purchase_count,
tg.amount as Total
from (select Card_Number,min(post_date) as mipd, max(post_date) as mxpd, count(*) as pc, sum(TX_Amount) as Amount from tx_table where TX_Type = 'Purchase' group by card_number) tg
join tx_table tt1 on tg.card_number=tt1.card_number and tg.mipd=tt1.post_date
join tx_table tt2 on tg.card_number=tt2.card_number and tg.mxpd=tt2.post_date
where TX_Type = 'Purchase'
I added the count .. I didn't see it first time.
If you need also the summary on multiple TX_Types, you have to take it from the where clause and put it in the group and the inner selection join. But I guess you need only for purchases
;with cte as
(
select
Card_Number,
TX_Type,
Post_Date,
TX_Amount,
row_number() over(partition by TX_Type, Card_Number order by Post_Date asc) as FirstP,
row_number() over(partition by TX_Type, Card_Number order by Post_Date desc) as LastP
from tx_table
)
select
F.Post_Date as "First Purchase Date",
F.TX_Amount as "First Purchase Amount",
L.Post_Date as "Last Purchase Date",
L.TX_Amount as "Last Purchase Amount",
C.CC as "# of Purchases",
C.Amount as "Total of All Purchases"
from (select Card_Number, TX_Type, count(*) as CC, sum(TX_Amount) as Amount
from cte
group by Card_Number, TX_Type) as C
inner join cte as F
on C.Card_Number = F.Card_Number and
C.TX_Type = F.TX_Type and
F.FirstP = 1
inner join cte as L
on C.Card_Number = L.Card_Number and
C.TX_Type = L.TX_Type and
L.LastP = 1

Resources