SQL Server 2016: Turn multiple lines into columns with the different iterations - sql

I have a table that has many InsuranceNo's for unique MemberIDs. If there are more than one InsuranceNo, I want the InsuranceNo's to shift to a column, so in the end there is one line per MemberID, with all the iterations of that ID's InsuranceNo's as a Column.
MemberID InsuranceNo
--------------------------
123456 dser
124571 jklh
123456 abcd
I want it to look like this:
MemberID InsuranceNo1 InsuranceNo2
-----------------------------------------------------
123456 dser abcd
124571 jklh
Thank you!

Yet another option... Just change "YourTable" to your actual table name.
Example
Declare #SQL varchar(max) = '
Select *
From (
Select MemberID
,Item = concat(''InsuranceNo'',row_number() over (Partition By MemberID Order By (Select NULL)))
,Value = InsuranceNo
From YourTable
) A
Pivot (max([Value]) For [Item] in (' + Stuff((Select ','+QuoteName(concat('InsuranceNo',ColNr))
From (Select Distinct ColNr=row_number() over (Partition By MemberID Order By (Select NULL)) from YourTable ) A
For XML Path('')),1,1,'') + ') ) p'
--Print #SQL
Exec(#SQL);
Returns
MemberID InsuranceNo1 InsuranceNo2
123456 dser abcd
124571 jklh NULL
If it helps wrap your head around PIVOT, the SQL Generated looks like this:
Select *
From (
Select MemberID
,Item = concat('InsuranceNo',row_number() over (Partition By MemberID Order By (Select NULL)))
,Value = InsuranceNo
From YourTable
) A
Pivot (max([Value]) For [Item] in ([InsuranceNo1],[InsuranceNo2]) ) p

I prefer a dynamic cross tab to the dynamic pivot. I find the syntax far less obtuse and it is super easy if you need to add additional columns. Here is I would go about tackling this. Of course in your case you don't need a temp table because you have an actual table to use.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
MemberID int
, InsuranceNo varchar(10)
)
insert #Something values
(123456, 'dser')
, (124571, 'jklh')
, (123456, 'abcd')
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by MemberID order by InsuranceNo) as RowNum
from #Something
)
select MemberID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by MemberID order by MemberID';
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 InsuranceNo end) as InsuranceNo' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from #Something
group by MemberID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute

Related

Multiple Rows into One Row multiple columns

We need to list all numbers as a flat data set, how can we do that?
Table Name: Telephone
ID TYPE NUMBER
==================================
123 MN 042153939
123 HN 2242116
123 MN 1234567890
123 HN 12345678
Create Table Telephone
(
ID Integer,
Type char(3),
Number Varchar(20)
);
insert into Telephone values
(123, 'MN', '042153939'),
(123, 'HN', '2242116'),
(123, 'MN', '1234567890'),
(123, 'HN', '12345678');
I want SQL to return data in this format
ID MN#1 Mn#2 HN#1 HN#2
================================================
123 042153939 1234567890 2242116 12345678
Dynamic approach
Init
DROP TABLE IF EXISTS #Telephone;
CREATE TABLE #Telephone(ID INT,Type CHAR(3),Number VARCHAR(20));
INSERT INTO #Telephone (ID,Type,Number) VALUES
(123, 'MN', '042153939'),
(123, 'HN', '2242116'),
(123, 'MN', '1234567890'),
(123, 'HN', '12345678');
The code
DECLARE #ColumnList NVARCHAR(MAX);
SELECT #ColumnList = STUFF((SELECT ',[' + RTRIM(t.[Type]) + '#'
+ CONVERT(NVARCHAR(255),ROW_NUMBER()OVER(PARTITION BY t.[Type] ORDER BY t.ID)) + ']'
FROM #Telephone t FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'')
;
DECLARE #sql NVARCHAR(MAX) = '';
SET #sql = N'
SELECT ID,' + #ColumnList + N'
FROM (
SELECT t.ID,t.Number, RTRIM(t.[Type]) + ''#'' + CONVERT(NVARCHAR(255),ROW_NUMBER()OVER(PARTITION BY t.[Type] ORDER BY t.ID)) AS [Type]
FROM #Telephone t
) a
PIVOT(MAX(a.Number) FOR a.Type IN (' + #ColumnList + N')) p
'
;
--PRINT #sql
IF #sql IS NOT NULL EXEC(#sql);
try pivoting like below :
SELECT first_column AS <first_column_alias>,
[pivot_value1], [pivot_value2], ... [pivot_value_n]
FROM
(<source_table>) AS <source_table_alias>
PIVOT
(
aggregate_function(<aggregate_column>)
FOR <pivot_column> IN ([pivot_value1], [pivot_value2], ... [pivot_value_n])
) AS <pivot_table_alias>;
We can try using a pivot query with the help of ROW_NUMBER():
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY TYPE DESC, NUMBER) rn
FROM Telephone
)
SELECT
ID,
MAX(CASE WHEN rn = 1 THEN NUMBER END) AS [MN#1],
MAX(CASE WHEN rn = 2 THEN NUMBER END) AS [MN#2],
MAX(CASE WHEN rn = 3 THEN NUMBER END) AS [HN#3],
MAX(CASE WHEN rn = 4 THEN NUMBER END) AS [HN#4]
FROM cte
GROUP BY ID;
You may try this. with row_number() and pivot.
For more info about pivot you may find this link PIVOT.
; with cte as (
select row_number() over (partition by type order by id ) as Slno, * from Telephone
)
, ct as (
select id, type + '#' + cast(slno as varchar(5)) as Type, values from cte
)
select * from (
select * from ct
) as d
pivot
( max(values) for type in ( [MN#1],[Mn#2],[HN#1],[HN#2] )
) as p

Putting comma separated values into separate or different columns

I have a table and values as shown here
create table #example(id int primary key identity, cols varchar(255))
insert into #example(cols) values('HI,HELLO,BYE,TC')
insert into #example(cols) values('WHAT,ARE,YOU,DOING,HERE')
I need the resultant output as shown in the picture
Note : There is no limit for values i.e dynamic
This is what you are expecting
Schema:
CREATE TABLE #EXAMPLE(ID INT PRIMARY KEY IDENTITY, COLS VARCHAR(255))
INSERT INTO #EXAMPLE(COLS) VALUES('HI,HELLO,BYE,TC')
INSERT INTO #EXAMPLE(COLS) VALUES('WHAT,ARE,YOU,DOING,HERE')
Do split those comma separated values into Rows and Apply pivot
SELECT * FROM (
SELECT id
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2
, SPLT.CLMS.value('.','VARCHAR(MAX)') AS LIST FROM (
select id
, CAST( '<M>'+REPLACE(cols,',','</M><M>')+'</M>' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes('/M') AS SPLT(CLMS)
)A
PIVOT
(
MAX(LIST) FOR ID2 IN ([1],[2],[3],[4],[5])
)PV
You will get result as
+----+------+-------+-----+-------+------+
| id | 1 | 2 | 3 | 4 | 5 |
+----+------+-------+-----+-------+------+
| 1 | HI | HELLO | BYE | TC | NULL |
| 2 | WHAT | ARE | YOU | DOING | HERE |
+----+------+-------+-----+-------+------+
Edit:
And now you need to go for dynamic pivot,since There is no limit for values.
DECLARE #COLS VARCHAR(MAX)='', #QRY VARCHAR(MAX)='';
SELECT #COLS =#COLS+'['+CAST( ID2 AS VARCHAR(10))+'],' FROM (
SELECT DISTINCT ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2 FROM (
select id, CAST( '<M>'+REPLACE(cols,',','</M><M>')+'</M>' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes('/M') AS SPLT(CLMS)
)A
SELECT #COLS = LEFT(#COLS,LEN(#COLS)-1)
SELECT #QRY =
'
SELECT * FROM (
SELECT id
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2
, SPLT.CLMS.value(''.'',''VARCHAR(MAX)'') AS LIST FROM (
select id, CAST( ''<M>''+REPLACE(cols,'','',''</M><M>'')+''</M>'' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes(''/M'') AS SPLT(CLMS)
)A
PIVOT
(
MAX(LIST) FOR ID2 IN ('+#COLS+ ')
)PV'
EXEC( #QRY)
Dynamic version base on split string then pivot table
Schema:
create table #example(id int primary key identity, cols varchar(255))
insert into #example(cols) values('HI,HELLO,BYE,TC')
insert into #example(cols) values('WHAT,ARE,YOU,DOING,HERE')
Calculate columns pivot
-- Calculate dynamic columns
;WITH temps AS
(
SELECT sd.* ,CAST('<x>' + replace(sd.cols, ',', '</x><x>') + '</x>' as xml) AS xmlText
FROM #example sd
),
temps1 AS
(
SELECT t.Id, t.cols, v.x.value('.','varchar(50)') AS Value
FROM temps t
CROSS APPLY
t.xmlText.nodes('/x') AS v(x)
)
SELECT #ColumnPivot = STUFF((SELECT DISTINCT CONCAT(',COL',row_number() OVER(PARTITION BY t.id ORDER BY t.Value)) FROM temps1 t FOR XML PATH('')), 1,1,'')
PRINT #ColumnPivot
Then PIVOT table
DECLARE #query nvarchar(max) =
CONCAT(N';WITH temps AS
(
SELECT sd.* ,CAST(''<x>'' + replace(sd.cols, '','', ''</x><x>'') + ''</x>'' as xml) AS xmlText
FROM #example sd
),
temps1 AS
(
SELECT t.Id, t.cols, v.x.value(''.'' , ''varchar(50)'') AS Value
FROM temps t
CROSS APPLY
t.xmlText.nodes(''/x'') AS v(x)
),
temps2 AS
(
SELECT t.* , CONCAT(''COL'',row_number() OVER(PARTITION BY t.id ORDER BY (select 1))) AS ColGroup
FROM temps1 t
)',
N'SELECT Id, cols, ' , #ColumnPiVot,
' FROM
(
select * from temps2
) AS src
PIVOT
(MAX(Value) FOR ColGroup IN (',#ColumnPiVot,')) AS pvt')
PRINT #query
exec sp_executesql #query
DROP TABLE #example
Demo link: Rextester
DECLARE #COLS AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX);
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(C.RN)
FROM (SELECT ID,TOKEN,COLS
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY (
SELECT NULL
)
) AS RN
FROM #EXAMPLE
CROSS APPLY (
SELECT *
FROM UDF_SPLITSTRING(COLS, ',')
) A) C
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #QUERY = 'SELECT ID,cols, ' + #COLS + ' FROM
(
SELECT ID,TOKEN,cols
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY (
SELECT NULL
)
) AS RN
FROM #EXAMPLE
CROSS APPLY (
SELECT *
FROM UDF_SPLITSTRING(COLS, '','')
) A
) X
PIVOT
(
MAX(TOKEN)
FOR rn IN (' + #COLS + ')
) P '
exec(#QUERY)
output
ID cols 1 2 3 4 5
1 HI,HELLO,BYE,TC HI HELLO BYE TC NULL
2 WHAT,ARE,YOU,DOING,HERE WHAT ARE YOU DOING HERE

SQL Server dynamic columns creation

I have a table with column and values as below
How do I fetch the result as in the second tabular column with the DYNAMIC column names as -- first with "prgmg_product_id" and the rest of the column as "source ID 1","source ID 2", "source ID 3"
To achieve this using Dynamic SQL, the below will help:
CREATE TABLE #Prgmg (
prgmg_product_id INT
,source_id_other INT
);
INSERT #Prgmg (
prgmg_product_id
,source_id_other
)
VALUES (3310,11478)
,(3337,10833)
,(3354,11466)
,(4039,4846)
,(4039,65454)
,(4039,65456);
DECLARE #DYColumns NVARCHAR(1000)
,#DYSqlQuery NVARCHAR(4000);
-- CREATE THE COLUMNS REQUIRED
SET #DYColumns = STUFF((
SELECT DISTINCT ','
+ N'sourceID'
+ CAST(ROW_NUMBER() OVER (PARTITION BY prgmg_product_id ORDER BY prgmg_product_id, source_id_other) AS NVARCHAR(10))
FROM #Prgmg
FOR XML PATH('')
), 1, 1, '');
-- CREATE THE DYNAMIC SQL AND ADD IN THE CREATED COLUMNS
SET #DYSqlQuery = '
SELECT prgmg_product_id,'
+ #DYColumns
+ ' FROM (
SELECT prgmg_product_id
,CAST(N''sourceID'' + CAST(ROW_NUMBER() OVER (
PARTITION BY prgmg_product_id ORDER BY prgmg_product_id, source_id_other
) AS NVARCHAR(10)) AS NVARCHAR(100)) AS Col
,source_id_other
FROM #Prgmg S1
) X
PIVOT(MIN(source_id_other) FOR Col IN (' + #DYColumns + ')) P'
EXECUTE sp_executesql #DYSqlQuery;
Whilst this does offer you the solution, you should spend time understanding the concepts used. Such as the creation of the Columns required using ROW_NUMBER and the how that maps to the use of the PIVOT.
I had a little time to I tossed this together. I know that around SO the preference is to use a dynamic pivot. I do not much care for PIVOT in sql server. I find the syntax to be very obtuse. I tend to prefer a cross tab query (also known as conditional aggregation) instead. The added benefit is that this approach is almost always slightly quicker than a dynamic PIVOT.
You also have to realize that more than half of the code posted here is setting up the problem. In the future you should post ddl and sample data in a consumable format like this. It makes it so much easier for us to help.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
prgmg_product_id int,
source_id_other int
)
insert #Something (prgmg_product_id, source_id_other) values
(3310, 11478),
(3337, 10833),
(3354, 11466),
(4039, 4846),
(4039, 65454),
(4039, 65456)
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by prgmg_product_id order by source_id_other) as RowNum
from #Something
)
select prgmg_product_id';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by prgmg_product_id order by prgmg_product_id desc';
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 source_id_other end) as SourceID' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from #Something
group by prgmg_product_id
order by COUNT(*) desc
)
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute

Merging rows to columns

I have the following situation (heavily abstracted, please ignore bad design):
CREATE TABLE dbo.PersonTest (Id INT, name VARCHAR(255))
INSERT INTO dbo.PersonTest
(Id, name )
VALUES (1, 'Pete')
, (1, 'Marie')
, (2, 'Sam')
, (2, 'Daisy')
I am looking for the following result:
Id Name1 Name2
1 Marie Pete
2 Daisy Sam
So, for each Id, the rows should be merged.
Getting this result I used the following query:
WITH PersonRN AS
(
SELECT *
, ROW_NUMBER() OVER(PARTITION BY Id ORDER BY name) RN
FROM dbo.PersonTest
)
SELECT PT1.Id
, PT1.name Name1
, PT2.name Name2
FROM PersonRN AS PT1
LEFT JOIN PersonRN AS PT2 -- Left join in case there's only 1 name
ON PT2.Id = PT1.Id
AND PT2.RN = 2
WHERE PT1.RN = 1
Which works perfectly fine.
My question is: Is this the best way (best in terms of performance and resilience)? If, for example, one of these Id's has a third name, this third name is ignored by my query. I'm thinking the best way to deal with that would be dynamic SQL, which would be fine, but if it can be done without dynamic, I would prefer that.
Aside from dynamic PIVOT, you can do this using Dynamic Crosstab, which I prefer for readability.
SQL Fiddle
DECLARE #sql1 VARCHAR(1000) = '',
#sql2 VARCHAR(1000) = '',
#sql3 VARCHAR(1000) = ''
DECLARE #max INT
SELECT TOP 1 #max = COUNT(*) FROM PersonTest GROUP BY ID ORDER BY COUNT(*) DESC
SELECT #sql1 =
'SELECT
ID' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN RN =' + CONVERT(VARCHAR(5), RN)
+ ' THEN name END) AS ' + QUOTENAME('Name' + CONVERT(VARCHAR(5), RN)) + CHAR(10)
FROM(
SELECT TOP(#max)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RN
FROM sys.columns
)t
ORDER BY RN
SELECT #sql3 =
'FROM(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY name)
FROM PersonTest
)t
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)

Iterating a SELECT statement in SQL Server 2008 using a WHILE LOOP

I have a database that gives an employeeID, a job, an effectiveDate, and a dept. If an employee has worked more than one job they will have an additional row of data. My goal is to compress the rows corresponding to each employee into one. Basically I need a query to that pulls from a db that looks like this:
EmpID Job EffDate Dept
001 QB 01-01-2001 OFF
001 LB 01-01-2010 DEF
001 K 01-01-2005 SPEC
002 HC 01-01-2007 STAFF
003 P 01-01-2001 SPEC
003 CB 01-01-2002 DEF
To output like this:
EmpID Job1 EffDate1 Dept1 Job2 EffDate2 Dept2 Job3 EffDate3 Dept3
001 QB 01-01-2001 OFF K 01-01-2005 SPEC LB 01-01-2010 DEF
002 HC 01-01-2007 STAFF
003 P 01-01-2001 SPEC CB 01-01-2002 DEF
So far I have done this:
SELECT
EmpNo
, Job
, EffDate
, Dept
, ROW_NUMBER() OVER (PARTITION BY EmpNo ORDER BY EffDate) AS RowNum
INTO #temp1
FROM JobHist
ORDER BY EffDate DESC
SELECT
JobHist.EmpNo
, JobHist.Job AS Job1
, JobHist.EjhJobDesc AS JobDesc1
, JobHist.EffDate AS EffDate1
, JobHist.Dept AS Dept1
, temp2.Job AS Job2
, temp2.EffDate AS EffDate2
, temp2.Dept AS Dept2
FROM #temp1 AS JobHist LEFT JOIN #temp1 AS temp2 ON JobHist.EmpNo = temp2.EmpNo AND temp2.RowNum = 2
WHERE JobHist.RowNum = 1
And that works just fine. The problem is that I need to make many columns, and I do not want to write all that code 20 times. So I want to iterate through using a WHILE command. Here is what I tried in that second SELECT statement:
DECLARE #Flag INT
DECLARE #FlagPlus INT
SET #Flag = 1
SET #FlagPlus = (#Flag + 1)
WHILE(#Flag < 20)
BEGIN
SELECT
temp#Flag.EmpNo
, temp#Flag.Job AS Job#Flag
, temp#Flag.EjhJobDesc AS JobDesc#Flag
, temp#Flag.EffDate AS EffDate#Flag
, temp#Flag.Dept AS Dept#Flag
FROM #temp1 AS temp#Flag
LEFT JOIN #temp#Flag AS temp#FlagPlus
ON temp#Flag.EmpNo = temp#FlagPlus.EmpNo AND temp#FlagPlus.RowNum = #FlagPlus
WHERE JobHist.RowNum = 1
SET #Flag = (#Flag + 1)
SET #FlagPlus = (#FlagPlus + 1)
END
I knew this probably wouldn't work because SQL will not understand the naming conventions I am trying to call each table and field. Is there a way using a cast or a concat command that I can automate the process so it just increments the numbers where I am asking it to?
First, let me maske clear that this is not directly an answer to the question. However, due to the large code block it is not suitable for a comment either, and I feel that it does add value to the question. So here it goes...
Having a dynamic number of columns is rarely a good solution. I'd opt for a different solution if using XML is an option:
SELECT
e.EmpNo,
(SELECT
h.Job,
h.EffDate,
h.Dept
FROM JobHist h
WHERE e.EmpNo = h.EmpNo
ORDER BY EffDate DESC
FOR XML PATH('job'), ROOT('jobs'), TYPE
) Jobs
FROM (SELECT DISTINCT EmpNo FROM JobHist) e
You can do an UNPIVOT and then a PIVOT of the data. this can be done either statically or dynamically:
Static Version:
select *
from
(
select empid, col + cast(rn as varchar(10)) colname, value
from
(
select Top 20 empid,
job,
convert(varchar(10), effdate, 101) effdate,
dept,
row_number() over(partition by empid order by effdate) rn
from yourtable
order by empid
) x
unpivot
(
value
for col in (Job, Effdate, Dept)
) u
) x1
pivot
(
min(value)
for colname in([Job1], [EffDate1], [Dept1],
[Job2], [EffDate2], [Dept2],
[Job3], [EffDate3], [Dept3])
)p
see SQL Fiddle with Demo
Dynamic Version:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#colsPivotName 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 ('empid')
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 empid order by effdate) rn
from yourtable
) t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('empid')
group by c.name, t.rn
order by t.rn, c.name desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select empid, col + cast(rn as varchar(10)) colname, value
from
(
select Top 20 empid,
job,
convert(varchar(10), effdate, 101) effdate,
dept,
row_number() over(partition by empid order by effdate) rn
from yourtable
order by empid
) x
unpivot
(
value
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
min(value)
for colname in ('+ #colspivot +')
) p'
exec(#query)
see SQL Fiddle with Demo
Here is the solution.No matter how many job changes for the Emp it will pivot all of them
If you want to Pivot only 20 then set #MAXCol =20
edit: forget parentheses around #SQL in last line
SELECT
EmpNo
, Job
, EffDate
, Dept
, ROW_NUMBER() OVER (PARTITION BY EmpNo ORDER BY EffDate) AS RowNum
INTO #temp1
FROM JobHist
ORDER BY EffDate DESC
DECLARE #MAXCol INT = (SELECT MAX(RowNum)FROM #temp1)
,#index INT =1
,#ColNames varchar(4000)=''
,#SQL VARCHAR(MAX)=''
WHILE (#index<=#MAXCol)
BEGIN
SET #ColNames =#ColNames +'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN Job END) as Job'+LTRIM(STR(#index))+','
+'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN EffDate END) as EffDate'+LTRIM(STR(#index))+','
+'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN Dept END) as Dept'+LTRIM(STR(#index))+','
SET #Index=#Index +1
END
SET #ColNames = LEFT(#ColNames,LEN(#ColNames)-1) -- Remove Last Comma
SET #SQL = 'SELECT EmpNo ,'+#ColNames+' FROM #temp1 GROUP BY EmpNo'
EXECUTE (#SQL)
Here is SQL Fiddle Demo working
http://sqlfiddle.com/#!3/99cea/1
Here's one way using a series of dynamically-created MAX/CASE expressions. You could also do this with PIVOT but this is quicker for me:
DECLARE #sql NVARCHAR(MAX) = N'SELECT EmpID';
SELECT TOP (20) #sql += N',
Job' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN Job END),
EffDate' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN EffDate END),
Dept' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN Dept END)'
FROM
(
SELECT rn = RTRIM(ROW_NUMBER() OVER (ORDER BY name))
FROM sys.all_objects
) AS x;
SET #sql += ' FROM (SELECT *, rn = ROW_NUMBER() OVER
(PARTITION BY EmpID ORDER BY EffDate) FROM dbo.your_table) AS y
GROUP BY EmpID;';
EXEC sp_executesql #sql;
You can probably tune this so that it determines the maximum number of job changes for any employee, rather than just defaulting to 20. You might also consider ordering the opposite way - surely an employee's last 20 job changes are more relevant than their first 20, if they've had more than 20.