Convert rows to columns considering multiple columns - sql

I have current base table:
and I need to convert it to this:
I already have a dynamic SQL in place which is giving me below result for the LTD values:
But I am not able to create dynamically the columns for the Aggregationname based on the columns LTD and BOY and set the post fix to the column name.
Find my SQL below:
create table #tempIE
(
AGGREGATIONNAME varchar(2),
LTD decimal,
BOY decimal,
MONTH int
)
insert into #tempIE values ('XX', 50, 45, 00)
insert into #tempIE values ('XX', 150, 145, 01)
insert into #tempIE values ('XX', 300, 295, 02)
insert into #tempIE values ('YY', 25, 20, 00)
insert into #tempIE values ('YY', 50, 45, 01)
insert into #tempIE values ('YY', 75, 70, 02)
insert into #tempIE values ('ZZ', 500, 495, 00)
insert into #tempIE values ('ZZ', 600, 595, 01)
insert into #tempIE values ('ZZ', 700, 695, 02)
SELECT *
FROM #tempIE
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.AGGREGATIONNAME)
FROM #tempIE c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT MONTH, ' + #cols + ' INTO ##tempIEnew from
(
select AGGREGATIONNAME
, LTD
--, BOY
, MONTH
from #tempIE
) x
pivot
(
max(LTD)
for AGGREGATIONNAME in (' + #cols + ')
) p '
;
execute(#query)
SELECT *
FROM ##tempIEnew
drop table #tempIE
drop table ##tempIEnew

As I mentioned in the comments, I much more prefer using conditional aggregation rather than the PIVOT operator, it's so much easier to work with as it's not got restrictive syntax.
With conditional aggregation, you can easily achieve a static pivot with the following:
SELECT IE.[MONTH],
MAX(CASE IE.AGGREGATIONNAME WHEN 'XX' THEN IE.LTD END) AS XX_LTD,
MAX(CASE IE.AGGREGATIONNAME WHEN 'YY' THEN IE.LTD END) AS YY_LTD,
/* ... */
MAX(CASE IE.AGGREGATIONNAME WHEN 'ZZ' THEN IE.BOY END) AS ZZ_BPY
FROM #tempIE IE
GROUP BY IE.[MONTH];
For a dynamic pivot, then you could achieve this with the following.
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Delim nvarchar(3) = N',' + #CRLF
SELECT #SQL = N'SELECT IE.[MONTH],' + #CRLF +
STRING_AGG(N' MAX(CASE IE.AGGREGATIONNAME WHEN ' + QUOTENAME(IE.AGGREGATIONNAME,'''') + N' THEN IE.LTD END) AS ' + QUOTENAME(CONCAT(IE.AGGREGATIONNAME,'_LTD')),#Delim) WITHIN GROUP (ORDER BY IE.AGGREGATIONNAME) + N',' + #CRLF +
STRING_AGG(N' MAX(CASE IE.AGGREGATIONNAME WHEN ' + QUOTENAME(IE.AGGREGATIONNAME,'''') + N' THEN IE.BOY END) AS ' + QUOTENAME(CONCAT(IE.AGGREGATIONNAME,'_BOY')),#Delim) WITHIN GROUP (ORDER BY IE.AGGREGATIONNAME) + #CRLF +
N'FROM #tempIE IE' + #CRLF +
N'GROUP BY IE.[MONTH];'
FROM (SELECT DISTINCT AGGREGATIONNAME
FROM #tempIE) IE;
--PRINT #SQL;--Your best friend
EXEC sys.sp_executesql #SQL;
Note I assume you are using a fully supported version of SQL Server (as it's not noted you aren't) and so there's no need to FOR XML PATH and 2 passes of the table. I also switch to creating the query inline, as attempting to that with a variable #Columns is a real pain with conditional aggregation; I actually don't recommend that method either cause it's respective (like PIVOT).
db<>fiddle

Related

how to create dynamic table

I have a dynamic pivoted query which generates a result set and I want to insert that data into a table. But the problem is columns are dropped or generated by the time. So by the time I cannot predict columns. That is why I created a dynamic pivoted dataset. So how to insert that data set into table?
One solution is to drop and recreate the table every time but I don't know how to do it. I tried CTE, TEMP table but EXEC support only select, insert, update, delete statement:
DECLARE #columns NVARCHAR(MAX), #sqlquery NVARCHAR(MAX), #orderby Nvarchar(MAX),#value Nvarchar(max);
SET #columns = N'';
SET #value=N'0'
SELECT #columns += N', ' + QUOTENAME([Note_Type])
FROM
(
SELECT distinct
No_T
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)as A order by No_T
SET #sqlquery = N'
Select
K._Number
,D.C_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select
_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select distinct
right(REPLICATE('+#value+',11) +_Number,11) as [_Number]
,No_t
,No_T_Des
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)AS J
pivot
(
count(No_T_Des) FOR [No_t] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
)P
)K
left join
[DS_DM].[dbo].[D_TABLE] D on k._Number = D._Number
';
EXEC sp_executesql #sqlquery
I've modified your code to reflect my proposed solution.
IF OBJECT_ID (N'NEW_TABLE', N'U') IS NOT NULL
BEGIN
DROP TABLE NEW_TABLE
END
DECLARE #columns NVARCHAR(MAX), #sqlquery NVARCHAR(MAX), #orderby Nvarchar(MAX),#value Nvarchar(max);
SET #columns = N'';
SET #value=N'0'
SELECT #columns += N', ' + QUOTENAME([Note_Type])
FROM
(
SELECT distinct
No_T
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)as A order by No_T
SET #sqlquery = N'
Select
K._Number
,D.C_Number
,' + STUFF(#columns, 1, 2, '') + '
INTO NEW_TABLE
from
(
select
_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select distinct
right(REPLICATE('+#value+',11) +_Number,11) as [_Number]
,No_t
,No_T_Des
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)AS J
pivot
(
count(No_T_Des) FOR [No_t] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
)P
)K
left join
[DS_DM].[dbo].[D_TABLE] D on k._Number = D._Number
';
EXEC sp_executesql #sqlquery
So I found the answers to my questions.
there are 2 fileds which remais static every time. so I've created ETL that drop that table and recreate every single day with those two fileds. and rest of the 66 columns which are dynamic (drops or newly created) every day. so i've made a cursor for that which loop through all of that categories and altering that table that've created and added that code to ETL
So bassically Every single day that ETL Runs Drop the existing table, Create new table with the 2 static filds and altering same table using cursor with the dynamic filds.

Insert query results into temp table

I've recently learned from here how to do PIVOT in SQL, and I actually took an example from the other question on here. It works perfectly.
However, I want to perform additional joins, after the query, but I am unable to insert into temporary table the results of query? How may I do that?
Create table
CREATE TABLE yt
([Store] int, [Week] int, [xCount] int)
;
INSERT INTO yt
([Store], [Week], [xCount])
VALUES
(102, 1, 96),
(101, 1, 138),
(105, 1, 37),
(109, 1, 59),
(101, 2, 282),
(102, 2, 212),
(105, 2, 78),
(109, 2, 97),
(105, 3, 60),
(102, 3, 123),
(101, 3, 220),
(109, 3, 87);
Perform pivoting query
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Week)
from yt
group by Week
order by Week
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT store,' + #cols + ' from
(
select store, week, xCount
from yt
) x
pivot
(
sum(xCount)
for week in (' + #cols + ')
) p '
execute(#query)
The result is
store 1 2 3
101 138 282 220
102 96 212 123
105 37 78 60
109 59 97 87
But Id like to have it in #temp table, and I tried placing INTO #temp before 'Execute Query' and before FROM statement within Query.
Any idea? I am aware of SELECT * INTO #temp FROM BlaBla but seems its diff with Queries.
You can create a global temp table dynamically
declare #tblName nvarchar(10)
set #tblName = N'##Temp' + cast(##spid as nvarchar(5))
declare #tblCreate nvarchar(max)
SET #tblCreate = N'create table ' + #tblName + ' ('
+ REPLACE(#cols,',',' int,') + N' int)'
EXECUTE sp_executesql #tblCreate
And then edit your #query to insert into the table.
set #query = 'INSERT INTO ' + #tblName + ' SELECT store,' + #cols + ' from
(
select store, week, xCount
from yt
) x
pivot
(
sum(xCount)
for week in (' + #cols + ')
) p;
drop table ' + #tblName
execute(#query)
The problem you're facing with select ... into #temp inside the execute is that the table gets created, but due to being in separate scope, it gets dropped immediately when the execute ends, so your procedure can't see it.
Your code will work, if you create the table before calling execute and just use insert into. You can check this in SQL Fiddle. This just causes the problem that if your query is dynamic, how to create the table so that it fits the query.
Trying to create logic where you dynamically adjust the number of columns in SQL is not simple to do, and really isn't something you should be doing. Better way would be to handle that in your presentation layer.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Week)
from yt
group by Week
order by Week
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT store,' + #cols + ' Into ##tempyt from
(
select store, week, xCount
from yt
) x
pivot
(
sum(xCount)
for week in (' + #cols + ')
) p '
DROP TABLE IF EXISTS ##tempyt;
execute(#query);
select * from TempDB.sys.##tempyt

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

Including default values on dynamic pivot

I am pivoting data in a table based on date, but need to return NULL for dates with no data.
Data in [dbo].[Metrics]:
The dynamic pivot SQL I am executing:
DECLARE #cols NVARCHAR(1000);
SELECT #cols =
STUFF((SELECT N'],[' + month_day
FROM (SELECT DISTINCT RIGHT(CONVERT(NCHAR(10), [Date], 126), 2)
FROM [Metrics]) AS O(month_day)
ORDER BY month_day
FOR XML PATH('')
), 1, 2, '') + N']';
DECLARE #sql NVARCHAR(2000);
SET #sql =
N'SELECT [Key], ' + #cols +
N' FROM (SELECT [Key], ' +
N' RIGHT(CONVERT(NCHAR(10), [Date], 126), 2) AS month_day, ' +
N' [Value] ' +
N' FROM [Metrics]) AS O ' +
N'PIVOT ' +
N'(SUM([Value]) FOR month_day IN (' + #cols + N')) AS P;';
EXECUTE(#sql);
...and the result set that dynamic SQL returns:
As you can see, since I do not have data for every day of the month it is simply not returning columns for those days. How can I have it return columns 10-23 and 28-31 with NULL for each cell? It should return columns 1-31 regardless of the actual month (e.g., 31 columns even when there are less than 31 days in a given month).
Any help is appreciated.
Then you don't really need that columns to be generated dynamically, since you always want them from 1 to 31. The easier way would be hardcoding those values on your pivot, really. Anyway, here is the way you can define your columns dynamically and still get all days:
SELECT #cols =
STUFF((SELECT N'],[' + CAST(number AS VARCHAR(2))
FROM (SELECT DISTINCT number
FROM master..[spt_values]
WHERE number BETWEEN 1 AND 31) AS O(number)
ORDER BY number
FOR XML PATH('')
), 1, 2, '') + N']';
And the version you really should be using is this:
SELECT *
FROM (SELECT [Key],
RIGHT(CONVERT(NCHAR(10), [Date], 126), 2) AS month_day,
[Value]
FROM [Metrics]) AS O
PIVOT (SUM([Value]) FOR month_day IN ([01],[02],[03],[04],[05],[06],[07],[08],[09],
[10],[11],[12],[13],[14],[15],[16],[17],[18],
[19],[20],[21],[22],[23],[24],[25],[26],[27],
[28],[29],[30],[31])) AS P;

Flattening a Single Table

I am looking for a very basic flattening. I have a table in the database which provides each field for the employee in a different row.
EX
EMPLOYEE GROUP_NAME
81 BNEEO55
81 BNELLIG
81 LPKAPE
81 HRFT
90 BNRETINV
....
I would like to create a view which reports the data as:
EMPLOYEE Group1 Group2 Group3 Group4 Group5
81 BNEEO55 BNELLIG LPKAPE HRFT NULL
90 NULL NULL NULL NULL BNRETINV
OR
EMPLOYEE BNEEO55 BNELLIG LPKAPE HRFT BNRETINV
81 YES YES YES YES NO
90 NO NO NO NO YES
You need to PIVOT the data, if you are using SQL-Server 2008 or later you can use the PIVOT function:
CREATE TABLE #T (Employee INT, Group_name VARCHAR(50))
INSERT #T VALUES (81, 'BNEEO55'), (81, 'BNELLIG'), (81, 'LPKAPE'), (81, 'HRFT'), (90, 'BNRETINV')
SELECT Employee,
COALESCE([BNEEO55],'No') AS [BNEEO55],
COALESCE([BNELLIG],'No') AS [BNELLIG],
COALESCE([BNRETINV],'No') AS [BNRETINV],
COALESCE([HRFT],'No') AS [HRFT],
COALESCE([LPKAPE],'No') AS [LPKAPE]
FROM ( SELECT *, 'Yes' [Data]
FROM #T
) d
PIVOT
( MAX(Data)
FOR Group_Name IN ([BNEEO55], [BNELLIG], [BNRETINV], [HRFT], [LPKAPE])
) pvt
But it sounds like you need to do it dynamically if your data is changing. Something like the following would work:
DECLARE #SQL NVARCHAR(MAX) = '',
#Cols NVARCHAR(MAX) = ''
SELECT #SQL = #SQL + ',' + QUOTENAME(Group_Name),
#Cols = #Cols + ',COALESCE(' + QUOTENAME(Group_Name) + ',''No'') AS ' + QUOTENAME(Group_Name)
FROM ( SELECT DISTINCT Group_Name
FROM #T
) T
SET #SQL = '
SELECT Employee, ' + STUFF(#Cols, 1, 1, '') +
'FROM ( SELECT *, ''Yes'' [Data]
FROM #T
) d
PIVOT
( MAX(Data)
FOR Group_Name IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt'
EXECUTE SP_EXECUTESQL #SQL
This builds up the same query as the first one, but means that when a new group name is added the query doesn't have to be altered.
EDIT
To create and call this as a stored procedure:
CREATE TABLE T (Employee INT, Group_name VARCHAR(50))
INSERT T VALUES (81, 'BNEEO55'), (81, 'BNELLIG'), (81, 'LPKAPE'), (81, 'HRFT'), (90, 'BNRETINV')
GO
CREATE PROCEDURE PivotT
AS
DECLARE #SQL NVARCHAR(MAX) = '',
#Cols NVARCHAR(MAX) = ''
SELECT #SQL = #SQL + ',' + QUOTENAME(Group_Name),
#Cols = #Cols + ',COALESCE(' + QUOTENAME(Group_Name) + ',''No'') AS ' + QUOTENAME(Group_Name)
FROM ( SELECT DISTINCT Group_Name
FROM T
) T
SET #SQL = '
SELECT Employee, ' + STUFF(#Cols, 1, 1, '') +
'FROM ( SELECT *, ''Yes'' [Data]
FROM T
) d
PIVOT
( MAX(Data)
FOR Group_Name IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt'
EXECUTE SP_EXECUTESQL #SQL
GO
EXECUTE PivotT