Getting multiple MAX values from one-to-many table - sql

I have two tables and I would like to get MAX(date) from one-to-many table. If there is no value, it should be NULL. Only way I know how to do it by making sub queries but if there is ~20 different types then 20 sub queries does not sound efficient enough. Is there any better way to do it?
Table A:
UserId | Name
1 | John
2 | Jane
Table B:
UserId | Type | Date
1 | A | 2015-01-01
1 | A | 2015-12-31
1 | B | 2015-01-01
1 | B | 2015-12-31
2 | B | 2015-06-06
1 | C | 2015-01-01
2 | C | 2015-09-09
Result:
UserId | Type A date | Type B date | Type C date
1 | 2015-12-31 | 2015-12-31 | NULL
2 | NULL | 2015-06-06 | 2015-09-09
Current solution:
SELECT UserId,
(SELECT MAX(Date) FROM B WHERE Type = 'A' AND B.UserId= A.UserId),
(SELECT MAX(Date) FROM B WHERE Type = 'B' AND B.UserId= A.UserId),
(SELECT MAX(Date) FROM B WHERE Type = 'C' AND B.UserId= A.UserId
AND Date > (SELECT MAX(Date) FROM B WHERE Type = 'B' AND B.UserId = A.UserId))
FROM A
Thank you for all quick answers! They work perfectly. I modified my question little bit since I noticed that I need to add some conditions on some types. For example. Type C should be only presented if it's bigger than type B.

To get the expected result you don't even need to join, simply do a group by and use case expressions do to conditional aggregation:
select userid,
max(case when type = 'A' then date end) Adate,
max(case when type = 'B' then date end) Bdate
from tableB
group by userid
If you also want the username, you can join tableA with the above query:
select a.name, b.Adate, b.Bdate
from tableA a
join (select userid,
max(case when type = 'A' then date end) Adate,
max(case when type = 'B' then date end) Bdate
from tableB
group by userid) b
on a.userid = b.userid
However, if the number of types is unknown, I'd group by the column type too. I.e return types in different rows.
select a.name, b.type, b.date
from tableA a
left join (select userid, type, max(date) date,
from tableB
group by userid, type) b
on a.userid = b.userid

You can do a conditional aggregation instead of the subqueries in the SELECT:
SELECT
a.UserId,
MAX(CASE WHEN b.Type = 'A' THEN b.date END) AS ADate,
MAX(CASE WHEN b.Type = 'B' THEN b.date END) AS BDate
FROM TableA a
LEFT JOIN TableB b
ON b.UserId = a.UserId
GROUP BY a.UserId;
However, the above will only work if you know the values of Type. If you don't, then you need to do it using a dynamic query.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
a.UserId' + CHAR(10) +
(SELECT DISTINCT
' , MAX(CASE WHEN b.Type = ''' + Type + ''' THEN b.Date END) AS ' + QUOTENAME(Type + 'Date') + CHAR(10)
FROM TableB
FOR XML PATH('')
) +
'FROM TableA a
LEFT JOIN TableB b
ON b.UserId = a.UserId
GROUP BY a.UserId;';
EXEC (#sql);
ONLINE DEMO
Reference: Cross Tabs and Pivots by Jeff Moden

Use Dynamic Pivot, no matter how many type you have, you dont need to change the query.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName = ISNULL(#ColumnName + ',','') + QUOTENAME([TYPE])
FROM (SELECT DISTINCT [TYPE] as [TYPE] FROM [master].[dbo].[YourTable]) AS TypeTable
SET #DynamicPivotQuery =
'SELECT USERID,' + #ColumnName+'
FROM [master].[dbo].[YourTable]
PIVOT(MAX([Date])
FOR [TYPE] IN (' + #ColumnName+')) AS PVTTable'
EXEC sp_executesql #DynamicPivotQuery
Result is like below :
USERID A B
1 2015-12-31 2015-12-31
2 NULL 2015-06-06
Edit : For type C,there are some condition:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #ColumnNameForDisplay AS NVARCHAR(MAX)
DECLARE #otherCondition AS NVARCHAR(MAX)
SELECT #ColumnName = ISNULL(#ColumnName + ',','') + QUOTENAME([TYPE])
FROM (SELECT DISTINCT [TYPE] as [TYPE] FROM [master].[dbo].[YourTable]) AS TypeTable
SELECT #ColumnNameForDisplay = ISNULL(#ColumnNameForDisplay + ',','') + [TYPE2]
FROM (SELECT DISTINCT [TYPE]+' as [Type '+[TYPE]+' date]' as [TYPE2] FROM [master].[dbo].[YourTable] where type <> 'c') AS TypeTable
SELECT #otherCondition = ' ,Case when c <= b then null else c END as [Type C date] '
SET #DynamicPivotQuery = '
SELECT USERID,'+#ColumnNameForDisplay+#otherCondition+' from(
SELECT USERID,' + #ColumnName+'
FROM [master].[dbo].[YourTable]
PIVOT(MAX([Date])
FOR [TYPE] IN (' + #ColumnName+')) AS PVTTable) as t'
EXEC sp_executesql #DynamicPivotQuery
Result is like below :
USERID Type A date Type B date Type C date
1 2015-12-31 2015-12-31 NULL
2 NULL 2015-06-06 2015-09-09

Related

Count in SQL statement

I have a table with the following data:
Comp ID Name Type
-----------------------
AAA D2222 Jon BR11
AAA D2222 Jon BR12
AAA D2865 Toe BR11
BBB D4151 Sue BR11
BBB D4151 Sue BR12
BBB D4151 Sue BR13
CCC D6080 Pete BR14
CCC D6723 Tom BR13
I want to write my SQL statement and display like table below
Comp BR11 BR12 BR13 BR14
---------------------------
AAA 2 1
BBB 1 1 1
CCC 1 1
But I only know to select for one Type, how can I do it for many Types ?
select
Comp, count(Type) as BR11
from
CCDL
where
Type = 'BR11'
group by
Comp
Thanks much !
Dynamic pivot is the best approach:
create table test (Comp varchar(3), ID varchar(10), Name varchar(10), Type varchar(10))
insert into test values ('AAA','D2222','Jon','BR11');
insert into test values ('AAA','D2222','Jon','BR12');
insert into test values ('AAA','D2865','Toe','BR11');
insert into test values ('BBB','D4151','Sue','BR11');
insert into test values ('BBB','D4151','Sue','BR12');
insert into test values ('BBB','D4151','Sue','BR13');
insert into test values ('CCC','D6080','Pete','BR14');
insert into test values ('CCC','D6723','Tom','BR13');
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Type)
FROM test c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Comp, ' + #cols + ' from
(
select Comp
, ID
, Type
from test
) x
pivot
(
count(ID)
for Type in (' + #cols + ')
) p '
execute(#query)
The result is
Comp BR11 BR12 BR13 BR14
AAA 2 1 0 0
BBB 1 1 1 0
CCC 0 0 1 1
You can use selective aggregates for this:
SELECT Comp
, COUNT(CASE WHEN type = 'BR11' THEN 1 END) br11
, COUNT(CASE WHEN type = 'BR12' THEN 1 END) br12
, ...
FROM CCDL
GROUP BY Comp
More about this: http://modern-sql.com/feature/filter
It's basically also a pivot technique: http://modern-sql.com/use-case/pivot
try the following code
declare #tab table (Comp varchar(50),Id varchar(50),Name varchar(50),Type varchar(50))
insert into #tab
Select 'AAA','D2222','Jon','BR11' Union ALL
Select 'AAA','D2222','Jon','BR12' Union ALL
Select 'AAA','D2865','Toe','BR11' Union ALL
Select 'BBB','D4151','Sue','BR11' Union ALL
Select 'BBB','D4151','Sue','BR12' Union ALL
Select 'BBB','D4151','Sue','BR13' Union ALL
Select 'CCC','D6080','Pete','BR14'Union ALL
Select 'CCC','D6723','Tom','BR13'
Select * from
(Select type,comp,count(*) cnt from #tab
group by type,Comp
)d
PIVOT
(Sum(Cnt) FOR Type in ([BR11],[BR12],[BR13],[BR14]))p
TRY THIS : Use CASE with SUM as below:
SELECT Comp,
SUM(CASE WHEN type = 'BR11' THEN 1 ELSE 0 END) br11,
SUM(CASE WHEN type = 'BR12' THEN 1 ELSE 0 END) br12,
SUM(CASE WHEN type = 'BR13' THEN 1 ELSE 0 END) br13,
SUM(CASE WHEN type = 'BR14' THEN 1 ELSE 0 END) br14
FROM CCDL
GROUP BY Comp
What you can do
SELECT Comp,
SUM(CASE WHEN type = 'BR11' THEN 1 ELSE 0 END) br11,
SUM(CASE WHEN type = 'BR12' THEN 1 ELSE 0 END) br12,
SUM(CASE WHEN type = 'BR13' THEN 1 ELSE 0 END) br13,
SUM(CASE WHEN type = 'BR14' THEN 1 ELSE 0 END) br14
FROM CCDL
GROUP BY Comp

SQL: Display the table and its job by yes and no

This is my table:
User Table
Id | Username |
---------------
1 | jdoe |
Job Table
Id | Job |
----------------
1 | Waiter |
2 | Office |
3 | Freelance |
User Job Table
Id |UserId | JobId |
--------------------
1 | 1 | 2 |
2 | 1 | 3 |
How to select this table and display it to look like this below:
Id | Username | Waiter| Office | Freelance|
-------------------------------------------
1 | jdoe | No | Yes | Yes |
This is a pretty standard pivot query question, with an additional slight twist. In case a given user does not have a certain type of job assigned, you want to display 'No'. One way to do this is to make use of COALESCE and replace NULL job aggregates.
SELECT u.Id,
u.Username,
COALESCE(MAX(CASE WHEN j.Job = 'Waiter' THEN 'Yes' END), 'No') AS Waiter,
COALESCE(MAX(CASE WHEN j.Job = 'Office' THEN 'Yes' END), 'No') AS Office,
COALESCE(MAX(CASE WHEN j.Job = 'Freelance' THEN 'Yes' END), 'No') AS Freelance
FROM User u
LEFT JOIN User_Job uj
ON u.Id = uj.UserId
LEFT JOIN Job j
ON uj.JobId = j.Id
GROUP BY u.Id,
u.Username
Try this..
WITH cte AS
(
SELECT u.username,job,CASE WHEN uj.jobid IS NULL THEN 'No' ELSE 'yes' END AS jobid
FROM USER u INNER JOIN UserJob uj on u.id = uj.Userid
RIGHT JOIN Job j on j.id = uj.jobid
)
SELECT username,ISNULL(waiter,'no') waiter,
ISNULL(Office,'no') Office,
ISNULL(Freelance,'no') Freelance
FROM cte
PIVOT
(
MAX(jobid) FOR job IN (Waiter, Office,Freelance)
) piv
WHERE username IS NOT NULL
DECLARE #cols AS NVARCHAR(MAX),#Listcols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + 'Isnull('+QUOTENAME(t.Job) +',''No'') as ' + t.Job
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #Listcols = STUFF((SELECT ',' + QUOTENAME(t.Job)
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT Id, Username,' + #cols + N' from
(
select D1.Id, D1.Username,
COALESCE(CASE WHEN isnull(D2.UserId,0) > 0 THEN ''Yes''
Else ''No''
End, ''No'') as UserId,
D3.Job
from [User] D1
Inner Join User_Job D2
On D1.Id = D2.UserId
Inner Join Job D3
On D2.JobId = D3.Id
) x
pivot
(
max(UserId)
for Job in (' + #Listcols + N')
) p '
exec sp_executesql #query;
If the job items are come from a result.
CREATE TABLE #T1(id INT ,Username VARCHAR(100))
INSERT INTO #T1 SELECT 1,'jdoe'
CREATE TABLE #T2(id INT ,Job VARCHAR(100))
INSERT INTO #T2 VALUES(1,'Waiter'),(2,'Office'),(3,'Freelance')
CREATE TABLE #T3(id INT ,UserId INT ,JobId INT )
INSERT INTO #T3 VALUES(1,1,2),(2,1,3)
DECLARE #col NVARCHAR(max),#sql NVARCHAR(max)
SELECT #col=ISNULL(#col+',[','[')+Job+']' FROM #t2
PRINT #col
SET #sql='
SELECT * FROM (
SELECT uj.Job,uj.Username,CASE WHEN t3.UserId IS NULL THEN ''No'' ELSE ''Yes'' END AS f FROM (
SELECT t2.id AS JobId, t2.Job,t1.id AS UserId, t1.Username
FROM #T1 AS t1,#t2 AS t2
) uj LEFT JOIN #T3 AS t3 ON t3.JobId = uj.JobId AND t3.UserId = uj.UserId
) AS t PIVOT(MAX(f) FOR job IN ('+#col+')) p'
EXEC(#sql)

How to use SQL Server 2005 Pivot based on lookup table

table [Status] has the following data:
ID Status
1 PaymentPending
2 Pending
3 Paid
4 Cancelled
5 Error
====================================
Data Table has the following structure:
ID WeekNumber StatusID
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 2
7 2 3
Looking for a Pivot
Week # PaymentPending Pending Paid Cancelled
Week 1 1 1 1 0
Week 2 1 2 1 0
SELECT 'Week '+CAST(coun.WeekNumber AS VARCHAR(10)) [Week #],[PaymentPending],[Pending],[Paid],[Cancelled],[Error] FROM
(SELECT [WeekNumber],[Status] FROM dbo.WeekDetails
INNER JOIN [dbo].[Status] AS s
ON [dbo].[WeekDetails].[StatusID] = [s].[ID]) AS wee
PIVOT (COUNT(wee.[Status]) FOR wee.[Status]
IN ([PaymentPending],[Pending],[Paid],[Cancelled],[Error])) AS Coun
A pivot might look like this:
SELECT * FROM
(SELECT
'Week ' + CAST(D.WeekNumber AS varchar(2)) [Week #],
S.Status
FROM DataTbl D
INNER JOIN Status S ON D.StatusID = S.ID
) Derived
PIVOT
(
COUNT(Status) FOR Status IN
([PaymentPending], [Pending], [Paid], [Cancelled]) -- add [Error] if needed
) Pvt
If you expect the number of items in theStatustable to change you might want to consider using a dynamic pivot to generate the column headings. Something like this:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = ISNULL(#cols + ',','') + QUOTENAME(Status)
FROM (SELECT ID, Status FROM Status) AS Statuses ORDER BY ID
SET #sql =
N'SELECT * FROM
(SELECT ''Week '' + CAST(D.WeekNumber AS varchar(2)) [Week #], S.Status
FROM Datatbl D
INNER JOIN Status S ON D.StatusID = S.ID) Q
PIVOT (
COUNT(Status)
FOR Status IN (' + #cols + ')
) AS Pvt'
EXEC sp_executesql #sql;
Sample SQL Fiddle
You can use CASE based aggregation with GROUP BY
SELECT 'Week ' + cast(WeekNumber as varchar(10)) as 'Week#',
SUM ( CASE WHEN StatusId =1 THEN 1 else 0 end) as 'PaymentPending',
SUM ( CASE WHEN StatusId =2 THEN 1 else 0 end) as 'Pending',
SUM ( CASE WHEN StatusId =3 THEN 1 else 0 end) as 'Paid',
SUM ( CASE WHEN StatusId =4 THEN 1 else 0 end) as 'Cancelled'
FROM DataTbl D
GROUP BY 'Week ' + cast(WeekNumber as varchar(10))

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

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