Dynamic pivot with similar column names - sql

I'm looking for a way to pivot a varying amount of rows to columns in sql server 2008 R2. I created the data column RANK in the query because, ultimately I want the pivoted column names to be labeled the value in the RANK column. Then, if somehow I can STUFF the other 3 field values together into one field I would be able to iterate over the row in my backend lang and split the field results appropriately.
Here is the current data set:
And I would like the end result of the pivot to produce a result like this:
I haven't found anything about being able to pivot in this "dynamic" way. Any help would be much appreciated.

As I mentioned above, you need to distinguish each of your Rank values. You've said that this is a calculated value that you can add a number to the end of each one. Once you've added that number, then you still need to pivot it.
The easiest way to first see this would be to write a hard-coded version of the query first.
Sample Data:
create table yourdata
(
id int,
code varchar(50),
created datetime,
[rank] varchar(50)
);
insert into yourdata
select 285856, 'J7609', '2015-01-19', 'Principle' union all
select 285856, 'J7613', '2015-01-19', 'Other' union all
select 285856, 'J0456', '2015-01-19', 'Other' union all
select 285856, 'J0694', '2015-01-19', 'Other' union all
select 285856, 'J1885', '2015-01-19', 'Other' union all
select 285856, 'J2060', '2015-01-19', 'Other' union all
select 285856, 'J2930', '2015-01-19', 'Other';
Static Query:
select Principle_1, Other_1,
Other_2, Other_3, Other_4,
Other_5, Other_6
from
(
-- using row_number to get unique id for each rank
select
data = cast(id as varchar(10)) +' | '+ code +' | '+ convert(varchar(10), created, 112),
[rank] = [rank] + '_' +cast(row_number() over(partition by id, [rank]
order by id) as varchar(10))
from yourdata
) d
pivot
(
max(data)
for [rank] in (Principle_1, Other_1, Other_2, Other_3, Other_4,
Other_5, Other_6)
) p;
Now to do this dynamic, you will create a sql string with the column names and then execute that string:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([rank] + '_' +cast(rn as varchar(10)))
from
(
select [rank],
rn = row_number() over(partition by id, [rank]
order by id)
from yourdata
) d
group by [rank], rn
order by rn, [rank] desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select
data = cast(id as varchar(10)) +'' | ''+ code +'' | ''+ convert(varchar(10), created, 112),
[rank] = [rank] + ''_'' +cast(row_number() over(partition by id, [rank]
order by id) as varchar(10))
from yourdata
) x
pivot
(
max(data)
for [rank] in (' + #cols + ')
) p '
exec sp_executesql #query;
This gets you a result:
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+
| Principle_1 | Other_1 | Other_2 | Other_3 | Other_4 | Other_5 | Other_6 |
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+
| 285856 | J7609 | 20150119 | 285856 | J7613 | 20150119 | 285856 | J0456 | 20150119 | 285856 | J0694 | 20150119 | 285856 | J1885 | 20150119 | 285856 | J2060 | 20150119 | 285856 | J2930 | 20150119 |
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+

Related

SQL How to pivot two columns of data into different columns?

This is the table I have:
| Scheme Code | MonthYear | Revenue | Revenue2 |
|-------------|-----------|---------|----------|
| 18VDA | 2018.1 | 100 | 50 |
| 18VDA | 2018.2 | 200 | 100 |
| 18VDA | 2018.3 | 200 | 150 |
and I want to pivot it to like this:
| Scheme Code | 2018.1 A | 2018.2 A | 2018.3 A | 2018.1 B | 2018.2 B | 2018.3 B |
|-------------|----------|----------|----------|----------|----------|----------|
| 18VDA | 100 | 200 | 200 | 50 | 100 | 150 |
How do I do it so that it pivots in MonthYear, but it duplicates it for both Revenue and Revenue2?
Thanks
EDIT: Messed up the output table I was hoping for! I've edited the actual output table I want to see!
EDIT 2:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME([MonthYear])
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT *
FROM ( SELECT [Scheme Code], MonthYear ,[Revenue]
FROM TableA
) a
PIVOT(sum(Revenue) for MonthYear in (' + #cols + ')
) as RevenueMonth
ORDER BY [Scheme Code]'
execute(#query);
This code I wrote will do it for just one column, and I get the output like this:
| Scheme Code | 2018.1 | 2018.2 | 2018.3 |
|-------------|--------|--------|--------|
| 18VDA | 100 | 200 | 200 |
My suggestion always is to try to write your query as a hard-coded or static version first before diving into dynamic SQL. This let's you get the final result you want with a smaller subset of data and you can verify that you have the logic correct.
I would tackle this by performing an UNPIVOT of the two Revenue columns first, then look at applying the PIVOT function. To UNPIVOT you can use either the UNPIVOT function or you can use CROSS APPLY with a UNION ALL to convert your two Revenue columns into a single column. A static version of the query would be similar to this:
select *
from
(
select
t.[Scheme Code],
new_colname = concat(t.[MonthYear], ' ', r.colname),
r.colvalue
from yourtable t
cross apply
(
select 'A', Revenue union all
select 'B', Revenue2
) r (colname, colvalue)
) d
pivot
(
sum(colvalue)
for new_colname in ([2018.1 A], [2018.2 A], [2018.3 A], [2018.1 B], [2018.2 B], [2018.3 B])
) p;
You'll notice that in the CROSS APPLY I added a column with the A or B that I use to identify either the Revenue or Revenue2 columns. This is then used to create the new column names for the PIVOT.
This should generate the result you want. Now to do this dynamically, you just need to convert the SQL to dynamic code. You can use the following to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME(concat([MonthYear], x.col))
from yourtable
cross join (select col = ' A' union all select ' B') x
group by [MonthYear], x.col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT *
FROM
(
select
t.[Scheme Code],
new_colname = concat(t.[MonthYear], '' '', r.colname),
r.colvalue
from yourtable t
cross apply
(
select ''A'', Revenue union all
select ''B'', Revenue2
) r (colname, colvalue)
) a
PIVOT
(
sum(colvalue) for new_colname in (' + #cols + ')
) as x
ORDER BY [Scheme Code]';
exec sp_executesql #query;
Both of these should generate the same results (dbfiddle demo)
Do it with CASE and dynamic sql.
DECLARE #colsA AS NVARCHAR(MAX),
#colsB AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsA = (SELECT ', sum(case [MonthYear] when ''' + [MonthYear] + ''' then Revenue end)' + QUOTENAME([MonthYear] + ' A')
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),
#colsB = (SELECT ', sum(case [MonthYear] when ''' + [MonthYear] + ''' then Revenue2 end)' + QUOTENAME([MonthYear] + ' B')
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)');
Set #query = 'select [Scheme Code]' + #colsA + #colsB + ' from TableA group by [Scheme Code] order by [Scheme Code];';
print #query;

Format Jagged data gained from dynamic pivot

I need to format and extract some data from a database. While I can extract the data successfully I am struggling with the jagged nature of it.
What I have is the following:
create table temp
(
QuestionID INT,
AnswerID INT,
AnswerValue NVARCHAR(50)
)
insert into temp values (1, 1, 'Ans C')
insert into temp values (1, 2, 'Ans B')
insert into temp values (1, 3, 'Ans A')
insert into temp values (2, 4, 'Ans D')
insert into temp values (2, 5, 'Ans E')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.AnswerID)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT QuestionID, ' + #cols + ' from
(
select QuestionID
, AnswerValue
, AnswerID
from temp
) x
pivot
(
max(AnswerValue)
for AnswerID in (' + #cols + ')
) p '
execute(#query)
drop table temp
Executed this produces
+------------+-------+-------+-------+-------+-------+
| QuestionID | 1 | 2 | 3 | 4 | 5 |
+------------+-------+-------+-------+-------+-------+
| 1 | Ans C | Ans B | Ans A | NULL | NULL |
| 2 | NULL | NULL | NULL | Ans D | Ans E |
+------------+-------+-------+-------+-------+-------+
I just need to format it like this
+------------+-------+-------+-------+
| QuestionID | Q1 | Q2 | Q3 |
+------------+-------+-------+-------+
| 1 | Ans C | Ans B | Ans A |
| 2 | NULL | Ans D | Ans E |
+------------+-------+-------+-------+
Note due to restrictions this needs to be done in SQL rather than an advanced language such as c#.
A few things are wrong with the code. First, you are creating your column list using the AnswerID so the data is being split across multiple columns instead of the Answer for each question.
In order to fix this, you'll want to use a windowing function like row_number() to create a sequence for each question/answer combination.
When creating your dynamic columns change the code to be:
SET #cols = STUFF((SELECT ',' + QUOTENAME('Q'+cast(rn as varchar(10)))
FROM
(
SELECT rn = row_number() over(partition by QuestionID
order by AnswerID)
FROM temp
) c
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
This will use row_number() and will create the column names based on the QuestionID. Then you'll include the row_number() in your subquery making your code:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT ',' + QUOTENAME('Q'+cast(rn as varchar(10)))
FROM
(
SELECT rn = row_number() over(partition by QuestionID
order by AnswerID)
FROM temp
) c
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT QuestionID, ' + #cols + '
from
(
select QuestionID
, AnswerValue
, col = ''Q''+ cast(row_number() over(partition by QuestionID
order by AnswerID) as varchar(10))
from temp
) x
pivot
(
max(AnswerValue)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| QUESTIONID | Q1 | Q2 | Q3 |
|------------|-------|-------|--------|
| 1 | Ans C | Ans B | Ans A |
| 2 | Ans D | Ans E | (null) |
You can use this part of code:
SELECT 'A' + CAST(ROW_NUMBER() OVER(PARTITION BY QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers
in order to generate the column names required. The above gives you sth like:
cName
-----
A1
A2
A3
A1
A2
You can subsequently use the above in your dynamic pivot to obtain the desired result:
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(a.cName)
FROM (
SELECT 'A' + CAST(ROW_NUMBER() OVER(PARTITION BY QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers
) a
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT Question, ' + #cols + ' ' +
'FROM (
SELECT q.Question, a.Answer,
''A'' + CAST(ROW_NUMBER() OVER(PARTITION BY a.QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers AS a
INNER JOIN tblQuestions AS q ON a.QuestionID = q.QuestionID
) t
PIVOT
(
MAX(t.Answer)
FOR cName in (' + #cols + ')
) Pvt '
execute(#query)
Output from above looks like:
Question A1 A2 A3
-----------------------------------
Q1 Answer1 Answer2 Answer3
Q2 Answer4 Answer5 NULL
SQL Fiddle demo here

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

how to convert row to column if unknown number of columns SQL server

I have data like this:
MaGiangVienID | SoTiet1 | SoTiet2 | DateID
79000G07.000206 | 60 | 60.00 | t11
79000G07.000206 | 54 | 54.00 | t12
I want to my result like this:
MaGiangVienID | SoTiet1_t11 | SoTiet2_t11 | SoTiet1_t12 | SoTiet2_t12
79000G07.000206 | 60 | 60.00 | 54 | 54.00
I don't know how many columns because MaGiangVienID have a lot of DateID
Please help me!!Thanks a lot.
For this type of data transformation you need to apply both the UNPIVOT and PIVOT functions. The UNPIVOT takes the data from the multiple columns and places it into rows and then the PIVOT takes the rows and converts it back to columns.
To perform the UNPIVOT all of the values that you convert to rows must be the same datatype so conversion might be needed.
Unpivot:
select MaGiangVienID,
value,
col +'_'+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in (SoTiet1, SoTiet2)
) unpiv
See SQL Fiddle with Demo. The result of the unpivot is:
| MAGIANGVIENID | VALUE | COL |
-----------------------------------------
| 79000G07.000206 | 60 | SoTiet1_t11 |
| 79000G07.000206 | 60.00 | SoTiet2_t11 |
| 79000G07.000206 | 54 | SoTiet1_t12 |
| 79000G07.000206 | 54.00 | SoTiet2_t12 |
As you see the UNPIVOT generates the new column names with the DateId appended to the end of it. Now you apply the PIVOT function.
Static PIVOT:
select MaGiangVienID, SoTiet1_t11, SoTiet2_t11, SoTiet1_t12, SoTiet2_t12
from
(
select MaGiangVienID,
value,
col +'_'+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in (SoTiet1, SoTiet2)
) unpiv
) src
pivot
(
max(value)
for col in (SoTiet1_t11, SoTiet2_t11, SoTiet1_t12, SoTiet2_t12)
) piv
See SQL Fiddle with Demo
Now the above version works great if you have a known number of DateId values but you stated that you don't so you will want to implement this same query using dynamic sql.
Dynamic PIVOT:
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 ('MaGiangVienID', 'DateID')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT DISTINCT ','
+ quotename(c.name + '_'+t.DateId)
from yourtable t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('MaGiangVienID', 'DateID')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select MaGiangVienID, '+#colsPivot+'
from
(
select MaGiangVienID, value, col +''_''+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
Both versions produce the same result:
The result is:
| MAGIANGVIENID | SOTIET1_T11 | SOTIET2_T11 | SOTIET1_T12 | SOTIET2_T12 |
---------------------------------------------------------------------------
| 79000G07.000206 | 60 | 60.00 | 54 | 54.00 |
If you do not have access to the UNPIVOT/PIVOT functions then you can replicate the query. The UNPIVOT function can be replicated using a UNION ALL and the PIVOT can be produced using a CASE statement with an aggregate function:
select MaGiangVienID,
max(case when col = 'SoTiet1_t11' then value end) SoTiet1_t11,
max(case when col = 'SoTiet2_t11' then value end) SoTiet2_t11,
max(case when col = 'SoTiet1_t12' then value end) SoTiet1_t12,
max(case when col = 'SoTiet2_t12' then value end) SoTiet2_t12
from
(
select MaGiangVienID, 'SoTiet1_t11' col, cast(SoTiet1 as varchar(50)) value
from yourtable
where DateID = 't11'
union all
select MaGiangVienID, 'SoTiet2_t11' col, cast(SoTiet2 as varchar(50)) value
from yourtable
where DateID = 't11'
union all
select MaGiangVienID, 'SoTiet1_t12' col, cast(SoTiet1 as varchar(50)) value
from yourtable
where DateID = 't12'
union all
select MaGiangVienID, 'SoTiet2_t12' col, cast(SoTiet2 as varchar(50)) value
from yourtable
where DateID = 't12'
) src
group by MaGiangVienID
See SQL Fiddle with Demo
All versions will produce identical results.

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