Aggregate dynamic columns in SQL Server - sql

I have a narrow table containing unique key and source data
Unique_Key
System
1
IT
1
ACCOUNTS
1
PAYROLL
2
IT
2
PAYROLL
3
IT
4
HR
5
PAYROLL
I want to be able to pick a system as a base - in this case IT - then create a dynamic SQL query where it counts:
distinct unique key in the chosen system
proportion of shared unique key with other systems. These systems could be dynamic and there are lot more than 4
I'm thinking of using dynamic SQL and PIVOT to first pick out all the system names outside of IT. Then using IT as a base, join to that table to get the information.
select distinct Unique_Key, System_Name
into #staging
from dbo.data
where System_Name <> 'IT'
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(System_Name)
FROM #staging
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Unique_Key, ' + #cols + ' into dbo.temp from
(
select Unique_Key, System_Name
from #staging
) x
pivot
(
count(System_Name)
for System_Name in (' + #cols + ')
) p '
execute(#query)
select *
from
(
select distinct Unique_Key
from dbo.data
where System_Name = 'IT'
) a
left join dbo.temp b
on a.Unique_Key = b.Unique_Key
So the resulting table is:
Unique_Key
PAYROLL
ACCOUNTS
HR
1
1
1
0
2
1
0
0
3
0
0
0
What I want is one step further:
Distinct Count IT Key
PAYROLL
ACCOUNTS
HR
3
67%
33%
0%
I can do a simple join with specific case when/sum statement but wondering if there's a way to do it dynamically so I don't need to specify every system name.
Appreciate any tips/hints.

You can try to use dynamic SQL as below, I would use condition aggregate function get pivot value then we might add OUTER JOIN or EXISTS condition in dynamic SQL.
I would use sp_executesql instead of exec to avoid sql-injection.
DECLARE #System_Name NVARCHAR(50) = 'IT'
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#parameter AS NVARCHAR(MAX);
SET #parameter = '#System_Name NVARCHAR(50)'
select DISTINCT System_Name
into #staging
from dbo.data t1
WHERE t1.System_Name <> #System_Name
SET #cols = STUFF((SELECT distinct ', SUM(IIF(System_Name = '''+ System_Name+''',1,0)) * 100.0 / SUM(IIF(System_Name = #System_Name,0,1)) ' + QUOTENAME(System_Name)
FROM #staging
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT SUM(IIF(System_Name = #System_Name,0,1)) [Distinct Count IT Key], ' + #cols + ' from dbo.data t1
WHERE EXISTS (
SELECT 1
FROM dbo.data tt
WHERE tt.Unique_Key = t1.Unique_Key
AND tt.System_Name = #System_Name
) '
EXECUTE sp_executesql #query, #parameter, #System_Name
sqlfiddle

When writing Dynamic Query, you start off with a non-dynamic query. Make sure you gets the result of the query is correct before you convert to dynamic query.
For the result that you required, the query will be
with cte as
(
select it.Unique_Key, ot.System_Name
from data it
left join data ot on it.Unique_Key = ot.Unique_Key
and ot.System_Name <> 'IT'
where it.System_Name = 'IT'
)
select [ITKey] = count(distinct Unique_Key),
[ACCOUNTS] = count(case when System_Name = 'ACCOUNTS' then Unique_Key end) * 100.0
/ count(distinct Unique_Key),
[HR] = count(case when System_Name = 'HR' then Unique_Key end) * 100.0
/ count(distinct Unique_Key),
[PAYROLL] = count(case when System_Name = 'PAYROLL' then Unique_Key end) * 100.0
/ count(distinct Unique_Key)
from cte;
Once you get the result correct, it is not that difficult to convert to dynamic query. Use string_agg() or for xml path for those repeated rows
declare #sql nvarchar(max);
; with cte as
(
select distinct System_Name
from data
where System_Name <> 'IT'
)
select #sql = string_agg(sql1 + ' / ' + sql2, ',' + char(13))
from cte
cross apply
(
select sql1 = char(9) + quotename(System_Name) + ' = '
+ 'count(case when System_Name = ''' + System_Name + ''' then Unique_Key end) * 100.0 ',
sql2 = 'count(distinct Unique_Key)'
) a
select #sql = 'with cte as' + char(13)
+ '(' + char(13)
+ ' select it.Unique_Key, ot.System_Name' + char(13)
+ ' from data it' + char(13)
+ ' left join data ot on it.Unique_Key = ot.Unique_Key' + char(13)
+ ' and ot.System_Name <> ''IT''' + char(13)
+ ' where it.System_Name = ''IT''' + char(13)
+ ')' + char(13)
+ 'select [ITKey] = count(distinct Unique_Key), ' + char(13)
+ #sql + char(13)
+ 'from cte;' + char(13)
print #sql;
exec sp_executesql #sql;
db<>fiddle demo

This solution changes the aggregation function of the PIVOT itself.
First, let's add a column [has_it] to #staging that keeps track of whether each Unique_Key has an IT row:
select Unique_Key, System_Name, case when exists(select 1 from data d2 where d2.Unique_Key=d1.Unique_Key and d2.System_Name='IT') then 1 else 0 end as has_it
into #staging
from data d1
where System_Name <> 'IT'
group by Unique_Key, System_Name
Now, the per-System aggregation (sum) of this column divided by the final total unique keys needed (example case=3) returns the requests numbers. Change the PIVOT to the following and it's ready as-is, without further queries:
set #query = ' select *
from
(
select System_Name,cnt as [Distinct Count IT Key],has_it*1.0/cnt as divcnt
from #staging
cross join
(
select count(distinct Unique_Key) as cnt
from dbo.data
where System_Name = ''IT''
)y
) x
pivot
(
sum(divcnt)
for System_Name in (' + #cols + ')
) p'

Related

Sql join right table multiple row to column

Sql how to join right table multiple row to column
Table Application_detail
Id appl_name status
1. Abc. 1
2. DEF. 1
Table Approve_detail
applicant_id. Remark. Approveby
1. Appr by village Village
1. Appr by dist. District
I want to join like
Id. Applname. Status. Village_remark. District_remark
1. Abc. 1. Appr by village. Appr by dist
If you have hardcoded list of ApproveBys, you can do this:
SELECT ad.Id,
ad.appl_name AS Applname,
ad.status,
vil.Remark as Village_remark,
dist.Remark as Distric_remark
FROM Application_detail ad
JOIN Approve_detail vil
ON vil.applicant_id = ad.id
AND vil.Approveby = 'Village'
JOIN Approve_detail dist
ON dist.applicant_id = ad.id
AND dist.Approveby = 'District'
Otherwise you need to build a dynamic SQL. Like this:
-- Filling in initial data
SELECT *
INTO #Application_detail
FROM
(
SELECT 1 Id, 'Abc' appl_name, 1 Status
UNION ALL
SELECT 2, 'DEF', 1
) t
SELECT *
INTO #Approve_detail
FROM
(
SELECT 1 applicant_id, 'Appr by village' Remark, 'Village' Approveby
UNION ALL
SELECT 1, 'Appr by dist.', 'District'
) t
-- Start of the code
SELECT DISTINCT Approveby
INTO #Approvers
FROM #Approve_detail;
DECLARE #SQL NVARCHAR(MAX),
#SELECT NVARCHAR(MAX),
#FROM NVARCHAR(MAX),
#WHERE NVARCHAR(MAX);
SET #SELECT = 'SELECT ad.Id, ad.appl_name AS Applname, ad.status'
SET #FROM = '
FROM #Application_detail ad'
SET #WHERE = '
WHERE ad.id IN (SELECT applicant_id FROM #Approve_detail)'
SELECT #SELECT += ', t' + CAST(rn AS VARCHAR) + '.Remark AS ' + Approveby + '_remark',
#FROM += '
LEFT JOIN #Approve_detail t' + CAST(rn AS VARCHAR) + '
ON t' + CAST(rn AS VARCHAR) + '.applicant_id = ad.id
AND t' + CAST(rn AS VARCHAR) + '.Approveby = ''' + Approveby + ''''
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY Approveby) rn, Approveby FROM #Approvers
) t
SET #SQL= #SELECT + #FROM + #WHERE;
EXEC SP_EXECUTESQL #SQL

Using pivot in SQL Server not returning desired output

I have two tables like this:
**tblTagDescription**
and **tblDataLog**
Now I want to show record of any specific group and in one there might be same group for multiple id in tbltagdescription. And id of tbltagdescription is foreign key for tblDataLog as TagDescID.
Here 'Group1' has 10 ID as from 1 to 10. and there might be multiple record for these ID (from 1 to 10) in tbldatalog. I want these ID from 1 to as columns. For this I used pivot:
DECLARE #COlsID NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Group NVARCHAR(50) = 'Group1'
IF OBJECT_ID('tempdb..##MYTABLE') IS NOT NULL
DROP TABLE ##MYTABLE
SELECT
#COlsID = COALESCE(#ColsID + '],[','') + CONVERT(NVARCHAR(5), z.TagDescID)
FROM
(SELECT DISTINCT TOP 50 tblDataLog.TagDescID
FROM tblDataLog
INNER JOIN tblTagDescription ON tblDataLog.TagDescID = tblTagDescription.ID
ORDER BY tblDataLog.TagDescID) z
SET #COlsID='[' + #COlsID + ']'
SET #SQL='select [DATE],SHIFT, ' + #COlsID + ' into ##MYTABLE from ( select [Date], Value,
(CASE
WHEN ((DATEPART(hour,[DATE]))>6 and (DATEPART(hour,[DATE]))<14) THEN ''A''
WHEN ((DATEPART(hour,[DATE]))>=14 and (DATEPART(hour,[DATE]))<22) THEN ''B''
WHEN ((DATEPART(hour,[DATE]))>=22 or (DATEPART(hour,[DATE]))<6) THEN ''C''
END )AS SHIFT
from tblDataLog )d pivot(max(Value) for TagDescID in (' + #COlsID + ')) piv;'
EXEC (#SQL)
Now when I execute this statement, I get an error:
Invalid column name 'TagDescID'
but there is this column in tbldatalog. How to solve this query?
You need TagDescID column in subquery.
DECLARE #COlsID NVARCHAR(MAX) = ''
DECLARE #COlsAlias NVARCHAR(MAX) = ''
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Group NVARCHAR(50) = 'Group1'
SELECT
#COlsID = #ColsID + ',' + z.TagDescID,
#COlsAlias = #COlsAlias + ',' + z.TagDescID + ' AS ' + z.ReportTag
FROM
(SELECT DISTINCT TOP 50 tblDataLog.TagDescID ID, QUOTENAME(CONVERT(NVARCHAR(5), tblDataLog.TagDescID )) TagDescID, QUOTENAME(tblTagDescription.ReportTag) ReportTag
FROM tblDataLog
INNER JOIN tblTagDescription ON tblDataLog.TagDescID = tblTagDescription.ID
ORDER BY tblDataLog.TagDescID
) z
SET #COlsID= STUFF(#COlsID,1,1,'')
SET #COlsAlias= STUFF(#COlsAlias,1,1,'')
SET #SQL='select [DATE],SHIFT, ' + #COlsAlias + ' into ##MYTABLE from ( select [Date], Value, TagDescID,
(CASE
WHEN ((DATEPART(hour,[DATE]))>6 and (DATEPART(hour,[DATE]))<14) THEN ''A''
WHEN ((DATEPART(hour,[DATE]))>=14 and (DATEPART(hour,[DATE]))<22) THEN ''B''
WHEN ((DATEPART(hour,[DATE]))>=22 or (DATEPART(hour,[DATE]))<6) THEN ''C''
END )AS SHIFT
from tblDataLog )d pivot(max(Value) for TagDescID in (' + #COlsID + ')) piv;'
EXEC (#SQL)

Query to pivot in SQL

I have data in the following format:
I need to pivot this to get the data as follows.
Please help!!!
Something like this. In this case you need to have a fixed list of names.
SELECT
SUM(CASE WHEN Student='Mike' THEN [English Mark] ELSE 0 END) as [Mike English Mark],
SUM(CASE WHEN Student='Mike' THEN [Maths Mark] ELSE 0 END) as [Mike Maths Mark],
SUM(CASE WHEN Student='Fisher' THEN [English Mark] ELSE 0 END) as [Fisher English Mark],
SUM(CASE WHEN Student='Fisher' THEN [Maths Mark] ELSE 0 END) as [Fisher Maths Mark],
SUM(CASE WHEN Student='John' THEN [English Mark] ELSE 0 END) as [John English Mark],
SUM(CASE WHEN Student='John' THEN [Maths Mark] ELSE 0 END) as [John Maths Mark],
[TestName]
FROM Table1
GROUP BY [Test Name]
The solution I got is a bit tricky but very dynamic.
You should first unpivot your table, and put the data in a temp table, after that I get the columns name for the pivoting and put the result in the #cols variable. At the end I create a dynamic sql string to pivot the the temp table that contains my data, so even if a new student gets added to the table his 2 columns will be generated in the end result.
select test, col + ' '+ Student stu_col , value
INTO
#temp
from Marks
unpivot(value for col in (english, maths)) unpiv
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(stu_col)
from #temp order by 1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT test, ' + #cols + ' from
(
select test, Value, stu_col
from #temp
) x
pivot
(
SUM(Value)
for stu_col in (' + #cols + ')
) p '
exec(#query)
DROP TABLE #temp
You can write a dynamic sql query using Pivot operator as:
DECLARE #columns NVARCHAR(MAX)
,#columnsEnglish_Mark NVARCHAR(MAX)
,#columnsMath_Mark NVARCHAR(MAX)
,#columnsFNL NVARCHAR(MAX)
,#sql NVARCHAR(MAX);
SET #columns = N'';
--Get column names for entire pivoting
SELECT #columns += N', ' + QUOTENAME(SpreadCol)
FROM (select distinct student as SpreadCol
from tblstudent
) AS T;
PRINT #columns;
--Get column names for Pivot1
SET #columnsEnglish_Mark = N'';
SELECT #columnsEnglish_Mark += N', ISNULL(' + QUOTENAME(SpreadCol) + ',0) AS [' + SpreadCol + '_English_Mark]'
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
;
PRINT #columnsEnglish_Mark;
--Get column names for Pivot2
SET #columnsMath_Mark = N'';
SELECT #columnsMath_Mark += N', ISNULL(' + QUOTENAME(SpreadCol) + ',0) AS [' + SpreadCol + '_Math_Mark]'
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
;
PRINT #columnsMath_Mark;
--Get final list of columns:
SET #columnsFNL = N'';
SELECT #columnsFNL += N', [' + SpreadCol + '_English_Mark], [' + SpreadCol + '_Math_Mark] '
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
order by T.SpreadCol asc; -- change ordering of columns here
PRINT #columnsFNL;
SET #sql = N'
select tblEnglish_Mark.Test_Name , ' + STUFF(#columnsFNL, 1, 2, '') + ' from
'
+
'
( SELECT Test_Name, ' + STUFF(#columnsEnglish_Mark, 1, 2, '') + '
FROM
(select student as SpreadCol , English_Mark, Test_Name
from tblstudent ) as D
PIVOT
(
sum(English_Mark) FOR SpreadCol IN ('
+ STUFF(REPLACE(#columns, ', [', ',['), 1, 1, '')
+ ')
) AS Pivot1 ) tblEnglish_Mark
inner join
'
+
'
( SELECT Test_Name, ' + STUFF(#columnsMath_Mark, 1, 2, '') + '
FROM
(select student as SpreadCol , Test_Name,Math_Mark
from tblstudent ) as D
PIVOT
(
MAx(Math_Mark) FOR SpreadCol IN ('
+ STUFF(REPLACE(#columns, ', [', ',['), 1, 1, '')
+ ')
) as Pivot2 ) tblMath_Mark
on tblEnglish_Mark.Test_Name = tblMath_Mark.Test_Name ;
'
;
PRINT #sql;
EXEC sp_executesql #sql;
Hope this helps!!!
Please try:
DECLARE #pivv NVARCHAR(MAX),#Query NVARCHAR(MAX)
SELECT #pivv=COALESCE(#pivv+',','')+ QUOTENAME(Student+'_English_Mark')+','+QUOTENAME(Student+'_Maths_Mark')
FROM YourTable GROUP BY Student
IF ISNULL(#pivv, '')<>''
SET #Query='SELECT* FROM(
select English_Mark Marks, Student+''_English_Mark'' Col, Test_Name From YourTable
union all
select Maths_Mark Marks, Student+''_Maths_Mark'' Col, Test_Name From YourTable
)x pivot (sum(Marks) for Col in ('+#pivv+')) as xx'
IF ISNULL(#Query, '')<>''
EXEC (#Query)
SQL Fiddle Demo

T-SQL Pivot - Total Row and Dynamic Columns

Let's jump straight into it. Here's the code
SELECT [prov], [201304], [201305], [201306], [201307]
FROM (
SELECT [prov], [arrival], [Amount]
FROM [tblSource]) up
PIVOT (SUM([Amount]) FOR [arrival] IN ([201304], [201305], [201306], [201307])) AS pvt
GO
It brings me back an ever so lovely table. I was wondering how I would get the totals for each "date" column to show in an appended last row?
In addition, the underlying table will have more data added, specifically more dates. This means that 201308 will be added next, then 201309 etc
This will mean that currently I will have to amend the code above each month to reflect the addition. Is there anyway around this?
You can dynamically create the columns using dynamic SQL, however, I would really recommend handling dynamic pivots in a layer designed for it, such as SSRS or excel.
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, ''Total'', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
This creates and executes the following SQL:
SELECT [Prov],
[2013-01-01] = ISNULL([2013-01-01], 0),
[2013-02-01] = ISNULL([2013-02-01], 0),
[Total] = ISNULL([2013-01-01], 0) + ISNULL([2013-02-01], 0)
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, 'Total', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN ([2013-01-01],[2013-02-01])
) pvt;
It is the query below union in the subquery up that adds the total row at the bottom, and the row total is simply created by adding all the columns in the row.
Example on SQL Fiddle
I will stress again though, I really recommend handling manipulation of data like this outside of SQL.
EDIT
An alternative to using the UNION to get the the total row is to use GROUPING SETS as follows:
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov = ISNULL(Prov, 'Total'), Amount = SUM(Amount)
FROM [tblSource]
GROUP BY GROUPING SETS((Prov, arrival), (arrival))
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
SAMPLE TABLE
CREATE TABLE #TEMP([prov] VARCHAR(100),[arrival] INT, AMOUNT NUMERIC(12,2))
INSERT INTO #TEMP
SELECT 'A' [prov],'201304' [arrival],100 AMOUNT
UNION ALL
SELECT 'A' ,'201305' ,124
UNION ALL
SELECT 'A' ,'201306' ,156
UNION ALL
SELECT 'B' ,'201304' ,67
UNION ALL
SELECT 'B' ,'201305' ,211
UNION ALL
SELECT 'B' ,'201306' ,176
UNION ALL
SELECT 'C' ,'201304' ,43
UNION ALL
SELECT 'C' ,'201305' ,56
UNION ALL
SELECT 'C' ,'201306' ,158
QUERY
You can use ROLLUP to get the row total. More about ROLLUP here
-- Get the columns for dynamic pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + CAST([arrival] AS VARCHAR(50)) + ']',
'[' + CAST([arrival] AS VARCHAR(50)) + ']')
FROM (SELECT DISTINCT [arrival] FROM #TEMP) PV
ORDER BY [arrival]
-- Replace NULL value with zero
DECLARE #NulltoZeroCols NVARCHAR (MAX)
SELECT #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+[arrival]+'],0) AS ['+[arrival]+']'
FROM (SELECT DISTINCT CAST([arrival] AS VARCHAR(50)) [arrival] FROM #TEMP)TAB
ORDER BY CAST([arrival]AS INT) FOR XML PATH('')),2,8000)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT [prov],' + #NullToZeroCols + ' FROM
(
SELECT
ISNULL([prov],''Total'')[prov],
SUM(AMOUNT)AMOUNT ,
ISNULL(CAST([arrival] AS VARCHAR(50)),''Total'')[arrival]
FROM #TEMP
GROUP BY [arrival],[prov]
WITH ROLLUP
) x
PIVOT
(
MIN(AMOUNT)
FOR [arrival] IN (' + #cols + ')
) p
ORDER BY CASE WHEN ([prov]=''Total'') THEN 1 ELSE 0 END,[prov]'
EXEC SP_EXECUTESQL #query
Note : If you do not want to replace NULL with zero, just replace #NullToZeroCols with #cols in outer query of dynamic pivot

Dynamic SQL to generate column names?

I have a query where I'm trying pivot row values into column names and currently I'm using SUM(Case...) As 'ColumnName' statements, like so:
SELECT
SKU1,
SUM(Case When Sku2=157 Then Quantity Else 0 End) As '157',
SUM(Case When Sku2=158 Then Quantity Else 0 End) As '158',
SUM(Case When Sku2=167 Then Quantity Else 0 End) As '167'
FROM
OrderDetailDeliveryReview
Group By
OrderShipToID,
DeliveryDate,
SKU1
The above query works great and gives me exactly what I need. However, I'm writing out the SUM(Case... statements by hand based on the results of the following query:
Select Distinct Sku2 From OrderDetailDeliveryReview
Is there a way, using T-SQL inside a stored procedure, that I can dynamically generate the SUM(Case... statements from the Select Distinct Sku2 From OrderDetailDeliveryReview query and then execute the resulting SQL code?
Having answered a lot of these over the years by generating dynamic pivot SQL from the metadata, have a look at these examples:
SQL Dynamic Pivot - how to order columns
SQL Server 2005 Pivot on Unknown Number of Columns
What SQL query or view will show "dynamic columns"
How do I Pivot on an XML column's attributes in T-SQL
How to apply the DRY principle to SQL Statements that Pivot Months
In your particular case (using the ANSI pivot instead of SQL Server 2005's PIVOT feature):
DECLARE #template AS varchar(max)
SET #template = 'SELECT
SKU1
{COLUMN_LIST}
FROM
OrderDetailDeliveryReview
Group By
OrderShipToID,
DeliveryDate,
SKU1
'
DECLARE #column_list AS varchar(max)
SELECT #column_list = COALESCE(#column_list, ',') + 'SUM(Case When Sku2=' + CONVERT(varchar, Sku2) + ' Then Quantity Else 0 End) As [' + CONVERT(varchar, Sku2) + '],'
FROM OrderDetailDeliveryReview
GROUP BY Sku2
ORDER BY Sku2
Set #column_list = Left(#column_list,Len(#column_list)-1)
SET #template = REPLACE(#template, '{COLUMN_LIST}', #column_list)
EXEC (#template)
I know that SO search engine is not perfect, but your question has been answered in SQL Server PIVOT Column Data.
Also see Creating cross tab queries and pivot tables in SQL.
Why do this using hard coded column names when you can pull all this dynamically from any table?
Using UNPIVOT and COALESCE, I can dynamically pull a list of columns from any table and associated column values for any record in a record listing and combine them in a list of column names with values by row. Here is the code. Just drop in your database and table name. The column/value table will be generated for you in SQL Server. Keep in mind, to get a shared column of values for the columns you want to convert to sql variant or text strings. But a great way to get a sample column list of values with matching column names and types with our while loops or cursors. Its pretty fast:
-- First get a list of all known columns in your database, dynamically...
DECLARE #COLUMNS nvarchar(max)
SELECT #COLUMNS =
CASE
WHEN A.DATA_TYPE = 'nvarchar' OR A.DATA_TYPE = 'ntext' THEN
COALESCE(#COLUMNS + ',','') + 'CAST(CONVERT(nvarchar(4000),['+A.[name]+']) AS sql_variant) AS ['+A.[name]+']'
WHEN A.DATA_TYPE = 'datetime' OR A.DATA_TYPE = 'smalldatetime' THEN
COALESCE(#COLUMNS + ',','') + 'CAST(CONVERT(nvarchar,['+A.[name]+'],101) AS sql_variant) AS ['+A.[name]+']'
ELSE
COALESCE(#COLUMNS + ',','') + 'CAST(['+A.[name]+'] AS sql_variant) AS ['+A.[name]+']'
END
FROM
(
SELECT
A.name,
C.DATA_TYPE
FROM YOURDATABASENAME.dbo.syscolumns A
INNER JOIN YOURDATABASENAME.dbo.sysobjects B ON B.id = A.id
LEFT JOIN
(
SELECT
COLUMN_NAME,
DATA_TYPE
FROM YOURDATABASENAME.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YOURTABLENAME'
) C ON C.COLUMN_NAME = A.name
WHERE B.name = 'YOURTABLENAME'
AND C.DATA_TYPE <> 'timestamp'
) A
-- Test that the formatted columns list is returned...
--SELECT #COLUMNS
-- This gets a second string list of all known columns in your database, dynamically...
DECLARE #COLUMNS2 nvarchar(max)
SELECT #COLUMNS2 = COALESCE(#COLUMNS2 + ',','') + '['+A.[name]+']'
FROM
(
SELECT
A.name,
C.DATA_TYPE
FROM YOURDATABASENAME.dbo.syscolumns A
INNER JOIN YOURDATABASENAME.dbo.sysobjects B ON B.id = A.id
LEFT JOIN
(
SELECT
COLUMN_NAME,
DATA_TYPE
FROM YOURDATABASENAME.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YOURTABLENAME'
) C ON C.COLUMN_NAME = A.name
WHERE B.name = 'YOURTABLENAME'
AND C.DATA_TYPE <> 'timestamp'
) A
-- Test that the formatted columns list is returned...
--SELECT #COLUMNS2
-- Now plug in the list of the dynamic columns list into an UNPIVOT to get a Column Name / Column Value list table...
DECLARE #sql nvarchar(max)
SET #sql =
'
SELECT
ColumnName,ColumnValue
FROM
(
SELECT
'+#COLUMNS+'
FROM YOURDATABASENAME.dbo.YOURTABLENAME
WHERE CHANGE_ID IN (SELECT ChangeId FROM YOURDATABASENAME.dbo.OperatorProcess WHERE OperatorProcessID = 3)
) AS SourceTable
UNPIVOT
(
ColumnValue FOR ColumnName IN ('+#COLUMNS2+')
) AS PivotTable
'
EXEC (#sql)
-- Darshankar Madhusudan i can do dynamic columnheading table easly...
--thanks
declare #incr int = 1,
#col int,
#str varchar(max),
#tblcrt varchar(max),
#insrt varchar(max),
set #tblcrt = 'DECLARE #Results table ('
set #str = ''
set #insrt = ''
select #col = max(column_id) From tempdb.sys.all_columns where object_id = object_id('tempdb.dbo.#aaa')
while #incr <= #col
BEGIN
SELECT #STR = #STR +case when #incr = 1 then '''' else ',''' end +rtrim(ltrim(NAME))+'''' FROM TEMPDB.SYS.ALL_COLUMNS WHERE OBJECT_ID = OBJECT_ID('TEMPDB.DBO.#AAA') and column_id = #incr
set #tblcrt = #tblcrt + case when #incr = 1 then '' else ',' end + 'Fld'+CAST(#incr as varchar(3)) +' varchar(50)'
set #insrt = #insrt + case when #incr = 1 then '' else ',' end + 'Fld'+CAST(#incr as varchar(3))
SET #INCR = #INCR + 1
END
set #tblcrt = #tblcrt + ')'
set #insrt = 'insert into #Results('+#insrt+') values (' + #STR +')'
set #tblcrt = #tblcrt+ ';' + #insrt + 'select * from #Results '
exec(#tblcrt)