SQL Server dynamic columns creation - sql

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

Related

SQL PIVOT 2 Columns and repeat for multiples - HEAD SCRATCHER

I am having a HECK of a time trying to pivot some data & am wondering if anyone has an idea that would solve this!
I have tried dynamic pivots, but I run out of columns fast.
I have tried multiple pivots and joining them, but that is very clunky.
I have the following data:
CREATE TABLE dbo.Visits
(
SourceID varchar(32),
VisitID varchar(32),
EpisodeDate varchar(9),
EpisodeUrnID int,
SortOrder int,
ProcID char(7)
);
INSERT dbo.Visits
(SourceID,VisitID,EpisodeDate,EpisodeUrnID,SortOrder,ProcID)
VALUES
('SKREE','B20190531064919932','20-May-19',1,1,'5A1955Z'),
('SKREE','B20190531064919932','20-May-19',1,2,'0BH17EZ'),
('SKREE','B20190531064919932','24-May-19',2,1,'03HY32Z'),
('SKREE','B20190531064919932','6-Jun-19' ,3,1,'03HY32Z'),
('SKREE','B20190531064919932','21-May-19',4,1,'02HV33Z'),
('SKREE','B20190531064919932','21-May-19',4,2,'B548ZZA'),
('SKREE','B20210530154407871','30-May-21',1,1,'0DTJ4ZZ'),
('SKREE','B20210530154407871','3-Jun-21' ,2,1,'0W9G40Z'),
('SKREE','B20210530154407871','3-Jun-21' ,2,2,'0WJG4ZZ'),
('SKREE','B20210530154407871','7-Jun-21' ,3,1,'02HV33Z'),
('SKREE','B20210530154407871','7-Jun-21' ,3,2,'B548ZZA');
Basically, for every VisitID, there are multiple EpisodeUrnIds, which can have multiple SortOrders and I need to list the EpisodeDate and ProcID of each on the same row.
I have analyzed our tables and one VisitID can have up to 40 EpisodeUrnIDs (so far), with each having up to 20 SortOrders (so far).
My goal is to get it to look like this (I used the first VisitID only in this example):
SourceID|VisitID|EpisodeDate|ProcID|EpisodeDate|ProcID|EpisodeDate|ProcID|EpisodeDate|ProcID|EpisodeDate|ProcID|EpisodeDate|ProcID
SKREE|B20190531064919932|20-May-19|5A1955Z|20-May-19|0BH17EZ|24-May-19|03HY32Z|6-Jun-19|03HY32Z|21-May-19|02HV33Z|21-May-19|B548ZZA
Thanks!
Dynamic PIVOTs are fun, there are definitely some different approaches to finding the limit. Here's one way:
DECLARE #sql nvarchar(max) = N'SELECT SourceID, VisitID';
;WITH x AS /* how many pivots do we need? */
(
SELECT TOP 1 c = COUNT(*) FROM dbo.Visits
GROUP BY SourceID, VisitID ORDER BY c DESC
),
n(n) AS /* produce that many rows for dynamic SQL */
(
SELECT 1 UNION ALL
SELECT n+1 FROM n WHERE n < (SELECT c FROM x)
)
SELECT #sql += N',
EpisodeDate' + CONVERT(varchar(11), n) + N' = MAX(CASE WHEN rn = '
+ CONVERT(varchar(11), n) + N' THEN EpisodeDate END),
ProcID' + CONVERT(varchar(11), n) + N' = MAX(CASE WHEN rn = '
+ CONVERT(varchar(11), n) + N' THEN ProcID END)'
FROM n OPTION (MAXRECURSION 32767); -- in case you go beyond 100
SET #sql += N' FROM src
GROUP BY SourceID, VisitID;';
SET #sql = N'
;WITH src AS
(
SELECT SourceID, VisitID, EpisodeDate, ProcID,
rn = ROW_NUMBER() OVER (PARTITION BY SourceID, VisitID ORDER BY SortOrder)
FROM dbo.Visits
)
' + #sql;
EXEC sys.sp_executesql #sql;
Working demo on dbfiddle
Article for more background

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

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

SQL Server multiple rows and two columns into single row with multiple columns

I have a table with a columns for case ID, Action, and reason.
a single case ID can have multiple rows with different actions and codes. I can pivot and get multiple rows with columns action1, action2, action3, etc., but for the life of me, can't get case id, action1, reason1, action2, reason2, etc on a single row.
If you need to go a little more dynamic (n reasons)
Drop Table #Temp
Declare #YourTable table (ID int,Action varchar(50),Reason varchar(50))
Insert Into #YourTable values
(1,'Load Data','Boss said to'),
(1,'Run Query','It is what I do'),
(2,'Take Garbage Out','Wife makes me')
-- Convert Data to EAV Structure'ish
Declare #XML xml = (Select *,GrpSeq = Row_Number() over (Partition By ID Order By (Select NULL)) from #YourTable for XML RAW)
Select ID = r.value('#ID','int')
,ColSeq = Row_Number() over (Partition By r.value('#ID','int') Order By (Select NULL))
,Element = attr.value('local-name(.)','varchar(100)')+r.value('#GrpSeq','varchar(10)')
,Value = attr.value('.','varchar(max)')
Into #Temp
From #XML.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','GrpSeq')
-- Get Cols in correct Order
Declare #Cols varchar(max)
Set #Cols = Stuff((Select ',' + QuoteName(Element)
From (Select Distinct Top 100 Percent ColSeq,Element From #Temp Order By ColSeq ) A
For XML Path(''), Type
).value('.', 'varchar(max)'),1,1,'')
-- Execute Dynamic Pivot
Declare #SQL varchar(max) = '
Select *
From (Select ID,Element,Value From #Temp) T
Pivot (
max(Value)
For [Element] in (' + #Cols + ')
) P '
Exec(#SQL)
Returns

Select numbered column

I have a dynamic select query which supposed to fetches columns as col1, col2......col9, col10 but it fetches incorrectly as col1, col10, col11, col12, col2.... Not sure how to select them as the query is dynamic.
Please help.
This is the dynamic select I have been using.
-- 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, '');
Drop Table #Prgmg
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)
,(13337,110833) -- Added to force over 10
,(13354,111466) -- Added to force over 10
,(14039,14846) -- Added to force over 10
,(14039,165454); -- Added to force over 10
DECLARE #DYColumns NVARCHAR(1000)
,#DYSqlQuery NVARCHAR(4000);
-- CREATE THE COLUMNS REQUIRED
SET #DYColumns = STUFF((
SELECT DISTINCT ','
+ N'sourceID'
+ right('00'+CAST(ROW_NUMBER() OVER (ORDER BY prgmg_product_id, source_id_other) AS NVARCHAR(10)),2)
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'
Print #DYSqlQuery
--EXECUTE sp_executesql #DYSqlQuery;
Returns -- Notice the Columns are in Order. This was done by zero padding the Row_Number()
SELECT prgmg_product_id,sourceID01,sourceID02,sourceID03,sourceID04,sourceID05,sourceID06,sourceID07,sourceID08,sourceID09,sourceID10 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 (sourceID01,sourceID02,sourceID03,sourceID04,sourceID05,sourceID06,sourceID07,sourceID08,sourceID09,sourceID10)) P
If you are using COALESCE or STUFF to build your columns, make sure there is an ORDER by
Furthermore, you could have col01,col02,..col10 to ensure the sequence

Split semicolon-delimited column into multiple columns

I'm trying to split a column, in SQL, into multiple columns.
My data looks like this:
Column1 | Column2 | Column3
ABC | 123 | User7;User9
nbm | qre | User1;User2;User3
POI | kjh | User1;User4;User5;User9
I need to split the Column3 into 4 new columns - each column containing the first "User". Each value within this column is separated by a semi-colon. One of the problems I have is that Column3 can have any number of users listed (all separated by semi-colons), so I don't know how many "new" columns I would need added.
The final output would need to look like:
Column1 | Column2 | Column3 | NewColumn1 | NewColumn2 | ETC.
Besides the fact that this is bad design here is a solution:
Just paste this into an empty query window and execute. Adapt to your needs...
declare #tbl TABLE(Column1 VARCHAR(15),Column2 VARCHAR(15),Column3 VARCHAR(150));
INSERT INTO #tbl VALUES
('ABC','123','User7;User9')
,('nbm','qre','User1;User2;User3')
,('POI','kjh','User1;User4;User5;User9');
WITH Splitted AS
(
SELECT Column1,Column2,CAST('<x>'+REPLACE(Column3,';','</x><x>')+'</x>' AS XML) AS Col3Splitted
FROM #tbl
)
SELECT Column1,Column2,Col3Splitted
,Col3Splitted.value('x[1]','varchar(max)') AS Column4
,Col3Splitted.value('x[2]','varchar(max)') AS Column5
,Col3Splitted.value('x[3]','varchar(max)') AS Column6
,Col3Splitted.value('x[4]','varchar(max)') AS Column7
/*Add as many as you need*/
FROM Splitted
Following the discussion with #SeanLang I add this dynamic approach. It will count the highest number of semicolons in Column3 and build the statement above dynamically.
CREATE TABLE #tbl (Column1 VARCHAR(15),Column2 VARCHAR(15),Column3 VARCHAR(150));
INSERT INTO #tbl VALUES
('ABC','123','User7;User9')
,('nbm','qre','User1;User2;User3')
,('POI','kjh','User1;User4;User5;User9');
DECLARE #sql VARCHAR(MAX)=
'WITH Splitted AS
(
SELECT Column1,Column2,CAST(''<x>''+REPLACE(Column3,'';'',''</x><x>'')+''</x>'' AS XML) AS Col3Splitted
FROM #tbl
)
SELECT Column1,Column2';
DECLARE #counter INT = 0;
WHILE #counter<=(SELECT MAX(LEN(Column3) - LEN(REPLACE(Column3, ';', ''))) from #tbl)
BEGIN
SET #counter=#counter+1;
SET #sql=#sql+',Col3Splitted.value(''x[' + CAST(#counter AS VARCHAR(10)) + ']'',''varchar(max)'') AS Column' + CAST(#counter+3 AS VARCHAR(10));
END
SET #sql=#sql+ ' FROM Splitted;';
EXEC (#sql);
DROP TABLE #tbl;
Here is a method that will be 100% dynamic. It will produce any number of columns based solely on the data it finds. The prevailing method for this around SO is a dynamic pivot. I prefer a dynamic crosstab as I find the syntax less obtuse and it has a slight benefit from a performance standpoint too. :)
Here is an article which explains this methodology very well. http://www.sqlservercentral.com/articles/Crosstab/65048/
Also, I am using the DelimitedSplit8K function originally penned by Jeff Moden and improved over time through the community. You can read about it and find the code for it here. http://www.sqlservercentral.com/articles/Tally+Table/72993/
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something;
create table #something
(
Column1 varchar(5)
, Column2 varchar(5)
, Column3 varchar(50)
);
insert #something
select 'ABC', '123', 'User7;User9' union all
select 'nbm', 'qre', 'User1;User2;User3' union all
select 'POI', 'kjh', 'User1;User4;User5;User9';
declare #StaticPortion nvarchar(2000) = 'with orderedResults as
(
select s.Column1
, s.Column2
, x.Item
, x.ItemNumber
from #something s
cross apply dbo.DelimitedSplit8K(Column3, '';'') x
)
select Column1
, Column2
';
declare #DynamicPortion nvarchar(max) = '';
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 ItemNumber = ' + CAST(N as varchar(6)) + ' then Item end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 COUNT(*)
from #something s
cross apply dbo.DelimitedSplit8K(Column3, ';') x
group by Column1
Order by COUNT(*) desc
);
declare #FinalStaticPortion nvarchar(2000) = ' from orderedResults group by Column1, Column2 order by Column1, Column2';
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
select #SqlToExecute;
--uncomment the following line when you are sure this is what you want to execute.
--exec sp_executesql #SqlToExecute;
For MySQl below code can be used. This is just a sample code
#num_Column3 := 1 + LENGTH(#Column3) - LENGTH(REPLACE(#Column3, '|', '')) AS num_Column3
,IF(#num_Column3 > 0, SUBSTRING_INDEX(SUBSTRING_INDEX(#Column3)), '|', 1), '|', -1), '') AS RC_user