Dynamic Cols Pivot Rows but only some columns, not all - sql

I'm making a dynamic pivot table with a similar structure in the example below.
Query to list each child record in columns of a parent
EXCEPT, all the examples I seem to find, when building the list of dynamics columns for the pivot, they all use "quotename" to get all columns in the table, while I only want a handful for my output.
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name +'_'+ cast(t.rn as varchar(10)))
from
(
select cast(row_number() over(partition by m.MemberID order by g.guestid) as varchar(50)) rn
from member m
left join guest g
on m.guestid = g.guestid
) t
cross apply sys.columns as C
where C.object_id = object_id('guest')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Wondering if anyone can help with the syntax. In the above example, instead of getting First name and Last Name, hypothetically, I only want Last Name. How would that look?
I'm just looking at how to replace the quotename properly, I believe I have the rest running properly.
SAMPLE:
Member Table
MemberID | FName | LName
001 Frank Smith
002 Mary Jane
003 John Henry
Guest Table
GuestID | FName | LName | MemberId
101 Steve Smith | 001
102 Peter Smith | 001
103 Mike Jane | 002
OUTPUT:
MemberID | FName | LName| GuestID1 | LName1 |GuestID2 | LName2
001 Frank Smith 101 Smith 102 Smith
002 Mary Jane 103 Jane
003 John Henry
Any and all help is much appreciated!

Key point is that we need to use row_number window function to make a row number and then use condition aggregate function to make it.
SELECT MemberID,FName,LName,
MAX(CASE WHEN rn = 1 THEN GuestID END) GuestID1,
MAX(CASE WHEN rn = 1 THEN g_LName END) LName1,
MAX(CASE WHEN rn = 2 THEN GuestID END) GuestID2,
MAX(CASE WHEN rn = 2 THEN g_LName END) LName2
FROM (
SELECT m.MemberID,
m.FName,
m.LName,
GuestID,
g.LName g_LName,
ROW_NUMBER() OVER(PARTITION BY m.MemberID ORDER BY GuestID) rn
FROM Member m LEFT JOIN Guest g
ON m.MemberId = g.MemberId
) t1
GROUP BY MemberID,FName,LName
if there might be multiple LName (more than 2) and you want to use Dynamic pivot you can try the below code, but that might be a little complex.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((
SELECT distinct ', MAX(CASE WHEN rn = '+ CAST(t1.cnt AS VARCHAR(5)) + ' THEN GuestID END)' + ' as '''+CONCAT(name,t1.cnt)+''''
FROM (SELECT COUNT(*) cnt FROM Guest GROUP BY MemberId) t1
CROSS JOIN (SELECT 'GuestID' name UNION ALL SELECT 'LName') t2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT MemberID,FName,LName, ' + #cols + '
FROM (
SELECT m.MemberID,
m.FName,
m.LName,
GuestID,
g.LName g_LName,
ROW_NUMBER() OVER(PARTITION BY m.MemberID ORDER BY GuestID) rn
FROM Member m LEFT JOIN Guest g
ON m.MemberId = g.MemberId
) t1
GROUP BY MemberID,FName,LName '
execute(#query)
sqlfiddle

Related

SQL Server 2008 Management Studio : convert row data into columns [duplicate]

This question already has answers here:
Transpose rows and columns with no aggregate
(2 answers)
Closed 8 years ago.
I have tried viewing other posts on the subject but all the examples I've seen are based on knowing a specific value.
Example of what I have:
Address Name Number
------- ------- -------
1234 Main Bob 555-555-5555
1234 Main Karen 444-444-4444
1990 Maple Susie 333-333-3333
1010 12th Joe 222-222-2222
1010 12th Beth 111-111-1111
1010 12th Steve 444-433-3221
Example of what I want:
Address Contact1 Contact2 Contact3
------- ------- -------- --------
1234 Main Bob:555-555-5555 Karen:444-444-4444 NULL
1990 Maple Susie:333-333-3333 NULL NULL
1010 12th Joe: 222-222-2222 Beth 111-111-1111 Steve 444-433-3221
There are thousands of rows so I can't CASE and.. I'm a more than a little lost here.
Any suggestions?
I think you can use the following query. This assumes you have maximum three contacts
;WITH orderedAddress(Address,Name,Number,Sort)
AS
(
SELECT Address,Name,Number,ROW_NUMBER() OVER(PARTITION BY Address ORDER BY Name) AS num
FROM Addresses
)
SELECT main.Address,Contact1.Name + ':' + Contact1.Number AS Contact1 ,
Contact2.Name + ':' + ISNULL(Contact2.Number,'') AS Contact2 ,
Contact3.Name + ':' + ISNULL(Contact3.Number,'') AS Contact3
FROM
(SELECT DISTINCT Address FROM Addresses) AS main
LEFT JOIN OrderedAddress Contact1 ON main.Address = Contact1.Address AND Contact1.Sort=1
LEFT JOIN OrderedAddress Contact2 ON main.Address = Contact2.Address AND Contact2.Sort=2
LEFT JOIN OrderedAddress Contact3 ON main.Address = Contact3.Address AND Contact3.Sort=3
A dynamic pivot will work but so will a dynamic cross tab. Generally speaking a cross tab will beat the pivot for performance. Here is an example of one I posted just a few days ago.
if OBJECT_ID('Something') is not null
drop table Something
create table Something
(
ID int,
Subject1 varchar(50)
)
insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
from Something
)
select ID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from Something
group by ID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute

SQL one to many on one row

I have a table that records the diagnosis of patient for each attendance. Patients can have more than one diagnosis.
I want one row per attendance with all diagnosis (up to 12 diagnosis). The below brings back more than one row.
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
from
Diagnosis dg
AttendanceID PatientNumber DiagnosisDate Diagnosis
10001 123456 01-Oct-13 A
10001 123456 01-Oct-13 B
10002 123456 20-Oct-13 D
It results should look like this:
AttendanceID PatientNumber DiagnosisDate Diagnosis 1 Diagnosis 2 Diagnosis 3
10001 123456 01-Oct-13 A B
10002 123456 20-Oct-13 D
Can someone please help?
You can get the result by implementing the PIVOT function, but I would also suggest using the windowing function row_number() to generate the number of diagnosis for each attendanceid and patientnumber:
select attendanceid, patientnumber,
diagnosisdate,
Diagnosis1, Diagnosis2, Diagnosis3
from
(
select attendanceid, patientnumber,
diagnosisdate, diagnosis,
'diagnosis'+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) d
pivot
(
max(diagnosis)
for seq in (Diagnosis1, Diagnosis2, Diagnosis3)
) piv;
See SQL Fiddle with Demo. Since you know you will have up to 12 diagnosis per patient/attendance you can easily hard code the query to add the additional columns.
But if you needed a dynamic version of the code, then you could use something similar to the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(seq)
from
(
select 'diagnosis'+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT attendanceid, patientnumber,
diagnosisdate,' + #cols + '
from
(
select attendanceid, patientnumber,
diagnosisdate, diagnosis,
''diagnosis''+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) x
pivot
(
max(diagnosis)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo
Both versions give a result:
| ATTENDANCEID | PATIENTNUMBER | DIAGNOSISDATE | DIAGNOSIS1 | DIAGNOSIS2 | DIAGNOSIS3 |
|--------------|---------------|--------------------------------|------------|------------|------------|
| 10001 | 123456 | October, 01 2013 00:00:00+0000 | A | B | (null) |
| 10002 | 123456 | October, 20 2013 00:00:00+0000 | D | (null) | (null) |
I am not 100% sure but it should be something along those lines.
;with cte as(
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
,ROW_NUMBER() over (partition by dg.attendanceID order by attendanceID) as seq1
,ROW_NUMBER() over (partition by dg.attendanceID, patient_no order by diagnosis) as seq2
from
Diagnosis dg
)
select
t1.attendanceID
,t1.patientNumber
,t1.diagnosisDate
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='1') as diag1
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='2') as diag2
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='3') as diag3
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='4') as diag4
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='5') as diag5
from cte t1
where seq1= 1
Below is a probable solution by using cursors i am not sure if you can show it as columns as it is variable for every patient but this will show diagnosis as comma separated in one single column
DECLARE #combinedString VARCHAR(MAX),
#id int,
#Diagnosis VARCHAR(MAX)
Declare #CONCATRESULT TABLE (AttendanceID int, Question VARCHAR(MAX) )
Declare Dia cursor for
SELECT AttendanceID FROM Diagnosis
open Dia
Fetch next from Dia into #id
while ##FETCH_STATUS=0
begin
SELECT #combinedString = COALESCE(#combinedString + ', ', '') + Diagnosis,
#id=AttendanceID FROM [Prod_PostData].[dbo].[KMSFPostDataDenorm] d
WHERE AttendanceID = #id
insert into #CONCATRESULT values ( #id ,#combinedString )
SET #combinedString = null
Fetch next from Dia into #id
end
close Dia
deallocate Dia
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
from
Diagnosis dg join (select * from #CONCATRESULT) Y on dg.AttendanceID=Y.AttendanceID

Select multiple rows into columns - dynamic pivot

Here is what the initial query returns:
plan_id Rep_id rep_nm employee_id Row_id
6720 35 Robert Jones 160 1
6720 36 Pam Smith 23 2
6720 37 Erik Johnson 85 3
6720 38 Sally Ells 212 4
6719 40 Barbara Wax 168 5
I need to get the rows to be as follows:
plan_id Rep_id1 rep_nm1 emp_id1 Rep_id2 rep_nm2 emp_id2 Rep_id3 etc.
6720 35 Robert Jones 160 36 Pam Smith 23 Erik Johnson
6719 40 Barbara Wax 168 NULL NULL NULL NULL
The maximum rep_id* columns will be 5. I tried searching for a pivot query for such an example but no success.
You did not specify what RDBMS you are using but if you are using SQL server, then you can implement both the UNPIVOT and PIVOT functions. If you know how many values you are going then you can hard-code the values:
select *
from
(
select plan_id, value, col+cast(rn as varchar(10)) col
from
(
select plan_id,
cast(rep_id as varchar(12)) rep_id,
rep_nm,
cast(employee_id as varchar(12)) employee_id,
row_number() over(partition by plan_id order by rep_id) rn
from yourtable
) src
unpivot
(
value
for col in (rep_id, rep_nm, employee_id)
) up
) un
pivot
(
max(value)
for col in ([rep_id1], [rep_nm1], [employee_id1],
[rep_id2], [rep_nm2], [employee_id2],
[rep_id3], [rep_nm3], [employee_id3],
[rep_id4], [rep_nm4], [employee_id4])
) piv
See SQL Fiddle with Demo
But if you have an unknown number of values, then you can use dynamic SQL similar to this:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('plan_id', 'Rep_id', 'Row_id')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name
+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by plan_id order by rep_id) rn
from yourtable
) t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('plan_id', 'Rep_id', 'Row_id')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select plan_id, value, col+cast(rn as varchar(10)) col
from
(
select plan_id,
cast(rep_id as varchar(12)) rep_id,
rep_nm,
cast(employee_id as varchar(12)) employee_id,
row_number() over(partition by plan_id order by rep_id) rn
from yourtable
) x
unpivot
(
value
for col in ('+ #colsunpivot +')
) u
) src
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
Both will produce the same result:
| PLAN_ID | EMPLOYEE_ID1 | REP_NM1 | EMPLOYEE_ID2 | REP_NM2 | EMPLOYEE_ID3 | REP_NM3 | EMPLOYEE_ID4 | REP_NM4 |
------------------------------------------------------------------------------------------------------------------------------
| 6719 | 168 | Barbara Wax | (null) | (null) | (null) | (null) | (null) | (null) |
| 6720 | 160 | Robert Jones | 23 | Pam Smith | 85 | Erik Johnson | 212 | Sally Ells |

rows into columns [duplicate]

This question already has answers here:
SQL turning values returned in 11 rows into 89 total columns
(2 answers)
Closed 9 years ago.
this is my query
select * from dbo.tblHRIS_ChildDetails where intSID=463
output:
intCHID intsid nvrchildname nvrgender dttchildDOB Occupation
3 463 SK Female 2001-12-11 00:00:00.000 Studying
4 463 SM Male 2007-10-08 00:00:00.000 Student
i need the output like this this is query is dynamic it may return n number of rows based on the intSID
chidname1 gender DOB childoccupation1 chidname2 gender DOB childoccupation2
SK female 2001-12-11 00:00:00.000 studying SM Male 2007-10-08 00:00:00.000 Student
For this type of data, you will need to implement both the UNPIVOT and then the PIVOT functions of SQL Server. The UNPIVOT takes your data from the multiple columns and place it into two columns and then you apply the PIVOT to transform the data back into columns.
If you know all of the values that you want to transform, then you can hard-code it, similar to this:
select *
from
(
select value, col+'_'+cast(rn as varchar(10)) col
from
(
select nvrchildname,
nvrgender,
convert(varchar(10), dttchildDOB, 120) dttchildDOB,
occupation,
row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
where intsid = 463
) src
unpivot
(
value
for col in (nvrchildname, nvrgender, dttchildDOB, occupation)
) unpiv
) src1
pivot
(
max(value)
for col in ([nvrchildname_1], [nvrgender_1],
[dttchildDOB_1], [occupation_1],
[nvrchildname_2], [nvrgender_2],
[dttchildDOB_2], [occupation_2])
) piv
See SQL Fiddle with Demo
Now, if you have an unknown number of values to transform, then you can use dynamic SQL for this:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('tblHRIS_ChildDetails') and
C.name not in ('intCHID', 'intsid')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name
+'_'+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
) t
cross apply sys.columns as C
where C.object_id = object_id('tblHRIS_ChildDetails') and
C.name not in ('intCHID', 'intsid')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select col+''_''+cast(rn as varchar(10)) col, value
from
(
select nvrchildname,
nvrgender,
convert(varchar(10), dttchildDOB, 120) dttchildDOB,
occupation,
row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
where intsid = 463
) x
unpivot
(
value
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
The result of both queries is:
| NVRCHILDNAME_1 | NVRGENDER_1 | DTTCHILDDOB_1 | OCCUPATION_1 | NVRCHILDNAME_2 | NVRGENDER_2 | DTTCHILDDOB_2 | OCCUPATION_2 |
-----------------------------------------------------------------------------------------------------------------------------
| SK | Female | 2001-12-11 | Studying | SM | Male | 2007-10-08 | Student |
You have to provide distinct column names but besides that, it's simple. But as others stated, this is not commonly done and looks like if you want print some report with two columns.
select
max(case when intCHID=1 then nvrchildname end) as chidname1,
max(case when intCHID=1 then gender end) as gender1,
max(case when intCHID=1 then dttchildDOB end) as DOB1,
max(case when intCHID=1 then Occupation end) as cildOccupation1,
max(case when intCHID=2 then nvrchildname end) as chidname2,
max(case when intCHID=2 then gender end) as gender2,
max(case when intCHID=2 then dttchildDOB end) as DOB2,
max(case when intCHID=2 then Occupation end) as cildOccupation2
from
dbo.tblHRIS_ChildDetails
where
intSID=463

How can I return several text fields with the same ID number in one row in SQL server?

I have table:
ID Note
1 1 aaa
2 1 bbb
3 1 ccc
4 2 ddd
5 2 eee
6 2 fff
I need to return it as:
ID Note1 Note2 Note3
1 1 aaa bbb ccc
2 2 ddd eee fff
Thank you!
You can use the PIVOT function for this type of query. If you have a known number of columns, then you can hard-code the values:
select *
from
(
select id, note,
'Note' +
cast(row_number() over(partition by id order by id) as varchar(10)) col
from yourtable
) x
pivot
(
max(note)
for col in ([Note1], [Note2], [Note3])
) p
See SQL Fiddle with Demo
If you are going to have an unknown number of notes that you want to turn into columns, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ QUOTENAME('Note' +
cast(row_number() over(partition by id order by id) as varchar(10)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id,' + #cols + ' from
(
select id, note,
''Note'' +
cast(row_number() over(partition by id order by id) as varchar(10)) col
from yourtable
) x
pivot
(
max(note)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Both will produce the same results.
| ID | NOTE1 | NOTE2 | NOTE3 |
------------------------------
| 1 | aaa | bbb | ccc |
| 2 | ddd | eee | fff |
Or if you do not want to use the PIVOT function, then you can use an aggregate function with a CASE statement:
select id,
max(case when rn = 1 then note else '' end) Note1,
max(case when rn = 2 then note else '' end) Note2,
max(case when rn = 3 then note else '' end) Note3
from
(
select id, note,
row_number() over(partition by id order by id) rn
from yourtable
) src
group by id
See SQL Fiddle with Demo