Dynamic SQL Pivot in SQL Server

SQL Server dynamic PIVOT query?

Dynamic SQL PIVOT:

create table temp
(
date datetime,
category varchar(3),
amount money
)

insert into temp values ('1/1/2012', 'ABC', 1000.00)
insert into temp values ('2/1/2012', 'DEF', 500.00)
insert into temp values ('2/1/2012', 'GHI', 800.00)
insert into temp values ('2/10/2012', 'DEF', 700.00)
insert into temp values ('3/1/2012', 'ABC', 1100.00)

DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);

SET @cols = STUFF((SELECT distinct ',' + QUOTENAME(c.category)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

set @query = 'SELECT date, ' + @cols + ' from
(
select date
, amount
, category
from temp
) x
pivot
(
max(amount)
for category in (' + @cols + ')
) p '

execute(@query)

drop table temp

Results:

Date                        ABC         DEF    GHI
2012-01-01 00:00:00.000 1000.00 NULL NULL
2012-02-01 00:00:00.000 NULL 500.00 800.00
2012-02-10 00:00:00.000 NULL 700.00 NULL
2012-03-01 00:00:00.000 1100.00 NULL NULL

Combination of dynamic pivot and static pivot in SQL Server

I have used your static pivot part of the query as the source of dynamic pivot. Create two sets of dynamic pivot column list. One for pivoting and the another with Coalesce() to select pivoted columns (to convert null into 0). If there is no categcount for any category then that category has been replaced with null (case when). Two more aliases for Category and SumCatCount have been created since those were used in pivot condition.

Here goes your answer:

 create table #temp
(
Place nvarchar(20),
State nvarchar(20),
Category nvarchar(20) null,
CategCount int null,
MCount int null,
Buys int,
Cost int
)

insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)


DECLARE @cols AS NVARCHAR(MAX)='';
DECLARE @query AS NVARCHAR(MAX)='';
DECLARE @colsForSelect AS NVARCHAR(MAX)='';

SET @cols = STUFF((SELECT distinct ',' + quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')


SET @colsForSelect = STUFF((SELECT distinct ',' + ' Coalesce('+quotename(category)+',0) '+ quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')


--select (@cols) as bm

set @query =
'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + @colsForSelect + ',SumCost,SumBuys from
(
select count(*) as count, place, state,category OldCategory, category,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(CategCount, 0)) as OldSumCatCount,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(Cost) as SumCost,
sum(ISNULL(buys, 0)) as SumBuys

from #temp
group by place , state, category
) src
pivot
(
max(SumCatCount) for Category in (' + @cols + ')
) piv
order by place desc,count'

execute(@query)
GO


















































countplacestateCategorySumMCountOldYoungSumCostSumBuys
2LondonUKOld5902625
1BrusselsBEYoung00234
2BrusselsBEnull700213

Dynamic Pivot Sql Query display all from one table

You can reconstruct the query

SELECT *  
FROM
(
SELECT A.[Phone], A.[CustNo], A.[Name], C.[BrandName], B.[qty]
FROM [Table-A] AS A
LEFT JOIN [Table-B] AS B
ON A.[CustNo] = B.[CustNo]
AND B.[odate] = '2021-04-22'
LEFT JOIN [Table-C] AS C on C.productid = B.Productid
WHERE A.[Route] = 1
) t
PIVOT
(
MIN([qty]) FOR [BrandName] IN ([Brand-1],[Brand-2],[Brand-3])
) AS piv

which contains LEFT JOIN rather than INNER JOIN, and STRING_AGG() function in order to generate the pivoted columns dynamically as in the following code block

DECLARE @cols  AS NVARCHAR(MAX),  @query AS NVARCHAR(MAX)

SET @cols = ( SELECT STRING_AGG(QUOTENAME([BrandName]),',')
FROM (SELECT DISTINCT [BrandName]
FROM [Table-C] ) C );

SET @query =
N'SELECT *
FROM
(
SELECT A.[Phone], A.[CustNo], A.[Name], C.[BrandName], B.[qty]
FROM [Table-A] AS A
LEFT JOIN [Table-B] AS B
ON A.[CustNo] = B.[CustNo]
AND B.[odate] = ''2021-04-22''
LEFT JOIN [Table-C] AS C on C.productid = B.Productid
WHERE A.[Route] = 1
) t
PIVOT
(
MIN([qty]) FOR [BrandName] IN (' + @cols + N')
) AS piv'

EXEC sp_executesql @query;

Demo

Dynamic TSQL Pivot without aggregate function

Sample data

create table ExternalPersonRelationTable
(
PersonId int,
SubjectCode int
);

insert into ExternalPersonRelationTable (PersonId, SubjectCode) values
(4187, 3),
(4187, 278),
(4429, 3),
(4429, 4),
(4463, 99),
(4464, 174),
(4464, 175);

Solution

Start with a (limited) static version of the pivot query as a reference.

select piv.SubjectCode as Code,
isnull(convert(bit, piv.[4187]), 0) as [4187],
isnull(convert(bit, piv.[4429]), 0) as [4429],
isnull(convert(bit, piv.[4463]), 0) as [4463],
isnull(convert(bit, piv.[4464]), 0) as [4464]
from ExternalPersonRelationTable epr
pivot (max(epr.PersonId) for epr.PersonId in ([4187],[4429],[4463],[4464])) piv;

Identify the dynamic parts and construct (and validate) those.

-- constuct lists
declare @fieldList nvarchar(1000);
declare @pivotList nvarchar(1000);

with cte as
(
select epr.PersonId
from ExternalPersonRelationTable epr
group by epr.PersonId
)
select @fieldList = string_agg('isnull(convert(bit, piv.['
+ convert(nvarchar(10), cte.PersonId)
+ ']), 0) as ['
+ convert(nvarchar(10), cte.PersonId)
+ ']', ', ') within group (order by cte.PersonId),
@pivotList = string_agg('['
+ convert(nvarchar(10), cte.PersonId)
+ ']', ',') within group (order by cte.PersonId)
from cte;

-- validate lists
select @fieldList as FieldList;
select @pivotList as PivotList;

Merge the dynamic parts in the final query (and validate during development phase).

-- construct query
declare @query nvarchar(3000) = 'select piv.SubjectCode as Code, '
+ @fieldList
+ 'from ExternalPersonRelationTable epr '
+ 'pivot (max(epr.PersonId) for epr.PersonId in ('
+ @pivotList
+ ')) piv;';

-- validate query
select @query as Query;

Run the dynamic query.

-- run query
exec sp_executesql @query;

Result

Code  4187   4429   4463   4464
---- ----- ----- ----- -----
3 True True False False
4 False True False False
99 False False True False
174 False False False True
175 False False False True
278 True False False False

Fiddle to see things in action.

Dynamic SQL Rolling 12 Months Pivot

You simply need to add a WHERE filter. This needs to be in both parts of the query.

DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);

SET @cols = STUFF(
(SELECT ',' + QUOTENAME(DATENAME(mm, EOMONTH(Date)) + ' of ' + DATENAME(year, EOMONTH(Date))) AS months_ago
FROM [TimeEntryList]
WHERE [Date] > DATEADD(year, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
GROUP BY EOMONTH(Date)
ORDER BY EOMONTH(Date)
FOR XML PATH(''), TYPE
).value('text()[1]', 'NVARCHAR(MAX)')
,1,1,'');

SET @query = '
SELECT
CustName,
' + @cols + '
from (
select
[CustName],
datename(mm,[Date])+'' of ''+datename(year,[Date])AS months_ago, [Hours] AS
NetQty
from [TimeEntryList]
WHERE [Date] > DATEADD(year, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
) as source
pivot
(
sum(NetQty) For months_ago in (' + @cols + ')
) as PivotTable;
';

execute sp_executesql @query;

You could even pass through a starting date parameter, like this:

DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX),
@dateFrom datetime;

SET @dateFrom = DATEADD(year, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1));

SET @cols = STUFF(
(SELECT ',' + QUOTENAME(DATENAME(mm,EOMONTH(Date)) + ' of ' + DATENAME(year,EOMONTH(Date)) AS months_ago
FROM [TimeEntryList]
WHERE [Date] > @dateFrom
GROUP BY EOMONTH(Date)
ORDER BY EOMONTH(Date)
FOR XML PATH(''), TYPE
).value('text()[1]', 'NVARCHAR(MAX)')
,1,1,'');

SET @query = '
SELECT
CustName,
' + @cols + '
from (
select
[CustName],
datename(mm,[Date])+'' of ''+datename(year,[Date])AS months_ago, [Hours] AS
NetQty
from [TimeEntryList]
WHERE [Date] > @dateFrom
) as source
pivot
(
sum(NetQty) For months_ago in (' + @cols + ')
) as PivotTable;
';

execute sp_executesql
@query
N'@dateFrom datetime',
@dateFrom = @dateFrom;

Dynamic SQL Pivot Causing Duplicate Columns

select * is including the columns you are pivoting; try this:

DECLARE @cols AS NVARCHAR(MAX), @query  AS NVARCHAR(MAX)
SELECT @cols = STUFF((SELECT distinct ',' + QUOTENAME(g.Name)
FROM Geometries g
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET @query =
'SELECT [RunId], [RunDataId], [Run #], [Test #], Description, data1, data2, data3, ' + @cols + '
FROM (
SELECT
r.Id as [RunId], rd.Id as [RunDataId], r.RunNumber as [Run #], r.TestNumber as [Test #], r.Description,
rd.data1, rd.data2, rd.data3,
g.Name[Geometry], g.Value
FROM dbo.Runs r
INNER JOIN RunDatas rd ON r.Id = rd.RunId
INNER JOIN RunGeometries rg ON rg.RunId = r.Id
INNER JOIN Geometries g ON g.Id = rg.GeometryId
) as data
PIVOT
(
Max(Value) FOR Geometry IN (' + @cols + ')
) as p'
print @query -- you can also take a look at the code you are generating to help troublshoot
execute sp_executesql @query

You can also check the code that is being generated with print @query or select @query.

SQL Server Pivot Dynamic Sql with two columns

I think you will need multiple PIVOT's. It's made slightly more complicated by a few things

  • Code/Label columns are not sequentially named CodeX,CodeY,...
  • Salary columns are sequentially named Salary1,Salary2,...
  • SELECT list columns must be grouped together "CodeX,Salary1,CodeY,Salary2....*

SET @cols = STUFF((SELECT ',' + QUOTENAME(c.important) +','+ QUOTENAME( 'custom' + CAST(ROW_NUMBER() OVER (ORDER BY c.id) as VARCHAR))

You had the right idea there, building a concatenated list of both columns. But you'll actually need 3 variables: 1) For code columns 2) for salary columns and 3) for select list.

Note: Must use the same ORDER BY in all queries

 DECLARE @LabelCols AS VARCHAR(MAX),
@SalaryCols AS VARCHAR(MAX),
@SelectCols AS VARCHAR(MAX),
@Query AS VARCHAR(MAX);

-- ** SQL Server 2012 doesn't support STRING_AGG()
SET @LabelCols = STUFF(( SELECT ',' + QUOTENAME(cd.label)
FROM code cd INNER JOIN client c ON c.fkcode = cd.id
ORDER BY cd.id
FOR XML PATH('')
),1,1,'')

SET @SalaryCols = STUFF(( SELECT ',' + QUOTENAME('Salary'+
CAST(ROW_NUMBER() OVER (ORDER BY cd.id) AS VARCHAR(20))
)
FROM code cd INNER JOIN client c ON c.fkcode = cd.id
FOR XML PATH('')
),1,1,'')

SET @SelectCols = STUFF(( SELECT ',' + QUOTENAME(cd.label)
+ ',' + QUOTENAME('Salary'+
CAST(ROW_NUMBER() OVER (ORDER BY cd.id) AS VARCHAR(20)))
FROM code cd INNER JOIN client c ON c.fkcode = cd.id
FOR XML PATH('')
),1,1,'')

Then use the three variable in your SELECT:

SET @Query = '
SELECT ClientId
, CodeId
, '+ @SelectCols +'
FROM
(
SELECT cd.id AS CodeId
, cd.label
, cd.labVal
, c.salary
, ''Salary''+ CAST(ROW_NUMBER() OVER (ORDER BY c.id) AS VARCHAR(20)) AS salaryLabel
FROM code cd
INNER JOIN client c ON c.fkcode = cd.id
) x
PIVOT
(
MAX(labVal)
FOR label IN (' + @LabelCols +')
) p1
PIVOT
(
MAX(salary)
FOR salaryLabel IN (' + @SalaryCols +')
) p2
'

EXECUTE (@Query)

Results:


CodeId | Important 1 | Salary1 | Important 2 | Salary2 | Important 3 | Salary3
-----: | :---------- | ------: | :---------- | ------: | :---------- | ------:
1 | Code1 | 120 | null | null | null | null
2 | null | null | Code2 | 1220 | null | null
3 | null | null | null | null | Code3 | 120

db<>fiddle here

dynamic pivot with parameter passed in

exec sp_executesql @Sql, N' @WeekStart Date, @WeekEnd Date', @WeekStart = @WeekStart, @WeekEnd = @WeekEnd


Related Topics



Leave a reply



Submit