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

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

Related

Dynamic Cols Pivot Rows but only some columns, not all

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

Join table rows as column values sql

In this situation I have two tables that I have joined, and it creates multiple duplicates due to each physician being able to have multiple licenses. I would like to pivot and make the multiple license state and numbers additional columns to table 1 as shown below.
This will need to be dynamically done since it can be an substantial amount of licenses assigned to one physician.
Table 1 looks like this.
assignid physician_name profession
-------------------------------
1 bob md
2 travis do
3 ryan md
4 pete pa
5 susan np
6 ashley cnp
Table 2
assignid license_state license_num
-------------------------------
1 oh 561
2 mi 987
3 ca 785
4 ny 965
4 mi 125
5 oh 369
5 ca 541
Joined
assignid physician_name profession license_state license_num
----------------------------------------------------------------
1 bob md oh 561
2 travis do mi 987
3 ryan md ca 785
4 pete pa ny 965
4 pete pa mi 125
5 susan np oh 369
5 susan np ca 541
I want to dynamically change the joined table to look like this.
assignid physician_name profession license_state1 license_num1 license_state2 license_num2
--------------------------------------------------------------------------------------------------
1 bob md oh 561
2 travis do mi 987
3 ryan md ca 785
4 pete pa ny 965 mi 125
5 susan np oh 369 ca 541
I attempted this route, but this gives me states as column header.
WITH pivotdata AS (
SELECT assignid,physician_name, profession, license_state,license_num
FROM dbo.Physicians p JOIN dbo.Licenses l ON p.AssignID = l.AssignID
)
SELECT *
FROM
pivotdata
PIVOT (MAX(license_num) FOR license_state IN ([oh], [mi], [ca],[ny])) TT;
Results
physician_name profession oh mi ca ny
-------------------------------------------
bob md 561
travis do 987
ryan md 785
pete pa 125 965
susan np 369 541
You can solve this using a dynamic cross tab. I learned this technique from Jeff Moden and his article here. http://www.sqlservercentral.com/articles/Crosstab/65048/
if OBJECT_ID('tempdb..#Physicians') is not null
drop table #Physicians
create table #Physicians
(
AssignID int
, PhysicianName varchar(20)
, Profession varchar(10)
)
insert #Physicians values
(1, 'bob', 'md')
, (2, 'travis', 'do')
, (3, 'ryan', 'md')
, (4, 'pete', 'pa')
, (5, 'susan', 'np')
, (6, 'ashley', 'cnp')
if OBJECT_ID('tempdb..#Licenses') is not null
drop table #Licenses
create table #Licenses
(
AssignID int
, LicenseState char(2)
, LicenseNum int
)
insert #Licenses values
(1, 'oh', 561)
, (2, 'mi', 987)
, (3, 'ca', 785)
, (4, 'ny', 965)
, (4, 'mi', 125)
, (5, 'oh', 369)
, (5, 'ca', 541)
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
SELECT p.AssignID
, p.PhysicianName
, p.Profession
, l.LicenseState
, l.LicenseNum
, ROW_NUMBER() over(partition by p.AssignID order by l.LicenseState) as RowNum
FROM #Physicians p
JOIN #Licenses l ON p.AssignID = l.AssignID
)
select AssignID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by AssignID order by AssignID';
--the following cte is a tally table (another trick I learned from Jeff Moden)
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
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then LicenseState end) as LicenseState' + CAST(N as varchar(6)) + CHAR(10)
+ ', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then LicenseNum end) as LicenseNum' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(p.AssignID)
FROM #Physicians p
JOIN #Licenses l ON p.AssignID = l.AssignID
group by p.AssignID
order by COUNT(*) desc
)
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
--you can comment the following. it is here for now so you can view the dynamic sql before it executes
select #SqlToExecute
--Once you are satisfied that the dynamic sql generated is correct simply uncomment the below line
--exec sp_executesql #SqlToExecute

Calculating averages in SQL Server without ignoring null values

I have a query like below:
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
--SELECT * FROM #t
SELECT
EmpName, [Dog], [Cat], [Goat], [Ram]
FROM
(SELECT
EmpName, Qty, Item
FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN ([Dog], [Cat], [Goat], [Ram])) AS p
And the result is as seen in the screenshot below:
I want to calculate the average Qty across Item without ignoring null values in the calculation. For example, in row 1, EmpName Abay should be 5 divided by 4 (number of columns), as seen in this screenshot:
How do I get the average column?
I'm not really familiar with the PIVOT query, so here is an alternative using conditional aggregation:
SELECT
Empname,
Dog = SUM(CASE WHEN Item = 'Dog' THEN Qty ELSE 0 END),
Cat = SUM(CASE WHEN Item = 'Cat' THEN Qty ELSE 0 END),
Goat = SUM(CASE WHEN Item = 'Goat' THEN Qty ELSE 0 END),
Ram = SUM(CASE WHEN Item = 'Ram' THEN Qty ELSE 0 END),
Average = SUM(ISNULL(Qty, 0))/ 4.0
FROM #t
GROUP BY EmpName;
Note that this will only work if you only have 4 Items. Otherwise, you need to resort to dynamic crosstab.
ONLINE DEMO
For dynamic crosstab, I used a temporary table instead of a table variable:
DECLARE #sql NVARCHAR(MAX) = '';
SELECT #sql =
'SELECT
Empname' + CHAR(10);
SELECT #sql = #sql +
' , SUM(CASE WHEN Item = ''' + Item + ''' THEN Qty ELSE 0 END) AS ' + QUOTENAME(Item) + CHAR(10)
FROM (
SELECT DISTINCT Item FROM #t
) t;
SELECT #sql = #sql +
' , SUM(ISNULL(Qty, 0)) / (SELECT COUNT(DISTINCT Item) * 1.0 FROM #t) AS [Average]' + CHAR(10) +
'FROM #t
GROUP BY EmpName;';
ONLINE DEMO
Try a combination of AVG and ISNULL, i.e. AVG(ISNULL(Dog, 0)).
One simple method is:
select empname, goat, cat, dog, ram,
(coalesce(goat, 0) + coalesce(cat, 0) + coalesce(dog, 0) + coalesce( ram, 0)
) / 4.0 as average
from t;
Another simple method uses outer apply:
select t.*, v.average
from t outer apply
(select avg(coalesce(x, 0))
from (values (t.goat), (t.cat), (t.dog), (t.ram)
) v(x)
) v(average);
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,p.total/4.0 as av
FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
EmpName Dog Cat Goat Ram av
---------- ----------- ----------- ----------- ----------- ---------------------------------------
Abay NULL NULL 5 NULL 1.250000
Carle 2 11 NULL NULL 3.250000
Jane 6 NULL 8 3 4.250000
V2: Dynamic script:
CREATE TABLE #t
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
INSERT #t ( EmpName, Qty, Item )VALUES('Abay',100,'abc')
DECLARE #cols VARCHAR(max),#sql VARCHAR(MAX),#cnt INT
SELECT #cols=ISNULL(#cols+',[','[')+Item+']',#cnt=ISNULL(#cnt+1,1) FROM #t GROUP BY Item
PRINT #cols
PRINT #cnt
SET #sql='SELECT EmpName, '+#cols+',p.total*1.0/'+LTRIM(#cnt)+' as av'+CHAR(13)
+' FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b'+CHAR(13)
+' PIVOT(SUM(Qty) FOR Item IN('+#cols+')) AS p'
EXEC(#sql)
EmpName abc Cat Dog Goat Ram av
---------- ----------- ----------- ----------- ----------- ----------- ---------------------------------------
Carle NULL 11 2 NULL NULL 2.600000
Jane NULL NULL 6 8 3 3.400000
Abay 100 NULL NULL 5 NULL 21.000000
Avoid NULL from your pivot sentence and compute AVG.
;with ct as
(
SELECT EmpName
, ISnull([Dog],0) Dog
, ISnull([Cat],0) Cat
, ISnull([Goat],0) Goat
, ISnull([Ram],0) Ram
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
)
select empname, avg(dog) dog, avg(cat) cat, avg(goat) goat, avg(ram) ram
from ct
group by empname;
+---------+-----+-----+------+-----+
| empname | dog | cat | goat | ram |
+---------+-----+-----+------+-----+
| Abay | 0 | 0 | 5 | 0 |
+---------+-----+-----+------+-----+
| Carle | 2 | 11 | 0 | 0 |
+---------+-----+-----+------+-----+
| Jane | 6 | 0 | 8 | 3 |
+---------+-----+-----+------+-----+
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,(isnull(p.cat,0)+isnull(p.dog,0)+isnull(p.Goat,0)+isnull(p.Ram,0))/4.0 as average
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p

SQL Server - Complex Dynamic Pivot multiple columns

FYI, this question is already answered but I have some new requirements which is very complex to implement so, I am posting it as a new question instead of editing the old question: (Previous Question)
I have two tables "Controls" and "ControlChilds" (in the ControlChilds table we have added a new column called ControlChildComments which we need to show in PIVOT output)
Parent Table Structure:
Create table Controls(
ProjectID Varchar(20) NOT NULL,
ControlID INT NOT NULL,
ControlCode Varchar(2) NOT NULL,
ControlPoint Decimal NULL,
ControlScore Decimal NULL,
ControlValue Varchar(50)
)
Sample Data
ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001 1 A 30.44 65 Invalid
P001 2 C 45.30 85 Valid
Child Table Structure:
Create table ControlChilds(
ControlID INT NOT NULL,
ControlChildID INT NOT NULL,
ControlChildValue Varchar(200) NULL,
ControlChildComments Varchar(200) NULL
)
Sample Data
ControlID | ControlChildID | ControlChildValue | ControlChildComments
1 100 Yes Something
1 101 No NULL
1 102 NA Others
1 103 Others NULL
2 104 Yes New one
2 105 SomeValue NULL
Based on my previous question (Previous Question) I got this output (You can refer to the PIVOT queries which produces this output in the answer given by #bluefeet. Thanks again #bluefeet.)
But now my requirement is changed and I need ControlChildComments after each Child values. For example, A_Child1, A_Child1Comments, A_Child2, A_Child2Comments etc...
Another tricky thing is I need to show the comments only when they are not null otherwise I shouldn't show the column. For example, in this case, it should be like this:
A_Child1, A_Child1Comments, A_Child2, A_Child3, A_Child3Comments, A_Child4, C_Child1, C_Child1Comments, C_Child2
Is this possible? I tried lot of things but the results are not accurate.
Since you now have multiple columns in your ControlChilds table that you need to PIVOT, you will need to use the similar method of unpivoting them first that you applied with the Controls table.
You will need to unpivot both the ChildControlValue and ChildControlComments using code similar to:
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value);
See SQL Fiddle with Demo. This gets your data in the format:
| PROJECTID | COL | VALUE |
|-----------|------------------|-----------|
| P001 | A_ChildValue1 | Yes |
| P001 | A_ChildComments1 | Something |
| P001 | A_ChildValue2 | No |
| P001 | A_ChildComments2 | (null) |
| P001 | A_ChildValue3 | NA |
You then use this code in your existing query:
select ProjectId,
A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2
from
(
select
ProjectId,
col = ControlCode +'_'+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select 'ControlPoint', cast(controlpoint as varchar(10)) union all
select 'ControlScore', cast(ControlScore as varchar(10)) union all
select 'ControlValue', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value)
) src
pivot
(
max(val)
for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2)
) piv;
See SQL Fiddle with Demo. Finally, you'll implement this in your dynamic SQL script:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select ControlCode,
col = ControlCode +'_'+col,
seq,
so
from controls
cross apply
(
select 'ControlPoint', 0, 0 union all
select 'ControlScore', 0, 1 union all
select 'ControlValue', 0, 2
) c (col, seq, so)
union all
select ControlCode,
col = ControlCode+'_'+subcol+cast(rn as varchar(10)),
rn,
so
from
(
select ControlCode,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', seq, 3 union all
select 'ChildComments', seq, 4
) c (subcol, rn, so)
) src
group by ControlCode, seq, col, so
order by ControlCode, seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ProjectId, ' + #cols + '
from
(
select ProjectId,
col = ControlCode +''_''+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select ''ControlPoint'', cast(controlpoint as varchar(10)) union all
select ''ControlScore'', cast(ControlScore as varchar(10)) union all
select ''ControlValue'', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+''_''+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select ''ChildValue'', ControlChildValue union all
select ''ChildComments'', ControlChildComments
) c (subCol, value)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both of these gives a result:
| PROJECTID | A_CONTROLPOINT | A_CONTROLSCORE | A_CONTROLVALUE | A_CHILDVALUE1 | A_CHILDCOMMENTS1 | A_CHILDVALUE2 | A_CHILDCOMMENTS2 | A_CHILDVALUE3 | A_CHILDCOMMENTS3 | A_CHILDVALUE4 | A_CHILDCOMMENTS4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILDVALUE1 | C_CHILDCOMMENTS1 | C_CHILDVALUE2 | C_CHILDCOMMENTS2 |
|-----------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|---------------|------------------|---------------|------------------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|
| P001 | 30.44 | 65.00 | Invalid | Yes | Something | No | (null) | NA | Others | Others | (null) | 45.30 | 85.00 | Valid | Yes | New one | SomeValue | (null) |
Here is an example of a dynamic crosstab. Since you have multiple columns you would need to adjust the dynamic portion of this to suit.
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