Dynamic pivot a 3 column table - sql

I'm trying to use dynamic pivot to have a column containing dates to be the column names.
I want this table:
App Date Count
Excel 2018-05-01 1
Excel 2018-05-02 1
Excel 2018-05-03 2
Word 2018-05-02 3
Word 2018-05-07 5
Word 2018-05-12 2
Paint 2018-05-07 6
to look like this:
2018-05-01 2018-05-02 2018-05-03 2018-05-07 2018-05-12
Excel 1 1 2 0 0
Word 0 3 0 5 2
Paint 0 0 0 6 0
I can't use a normal pivot as I don't know how many or what the dates will actually be. Each app can have a different number of rows. This table isn't just a SELECT * FROM TABLE either, it's made up of subqueries and CTEs so is a little complicated to work with.
Any help is appreciated. Let me know if you need more information.

Using dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(App varchar(50), [Date] varchar(50), [Count] int)
truncate table dbo.test
insert into dbo.test values
('Excel', '2018-05-01', 1),
('Excel', '2018-05-02', 1),
('Excel', '2018-05-03', 2),
('Word ', '2018-05-02', 3),
('Word ', '2018-05-07', 5),
('Word ', '2018-05-12', 2),
('Paint', '2018-05-07', 6)
declare #dates nvarchar(max)='' --holds all the dates that will become column names
declare #dates_aliases nvarchar(max)='' --holds the headers without NULL values
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
select #dates = #dates + ', [' + CONVERT(char(10), [date],126)+ ']' from dbo.test
group by [date]
select #dates_aliases = #dates_aliases + ', isnull(['
+ CONVERT(char(10), [date],126)+ '], 0) as ['
+ CONVERT(char(10), [date],126)+ ']'
from dbo.test group by [date]
set #dates = RIGHT(#dates, len(#dates)-2)
set #dates_aliases = RIGHT(#dates_aliases, len(#dates_aliases)-2)
set #sql = #sql + ' select piv.[App], ' + #dates_aliases
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [App], [Date], [Count] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot '
set #sql = #sql + ' ( '
set #sql = #sql + ' max([Count]) '
set #sql = #sql + ' for [Date] in ('+#dates+') '
set #sql = #sql + ' ) piv '
exec(#sql)
Results:

Try this:
SELECT A.*
INTO #TEMP
FROM
(
SELECT 'Excel' as app,'2018-05-01' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-02' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-03' as 'Date',2 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-02' as 'Date', 3 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-07' as 'Date', 5 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-12' as 'Date', 2 as 'Count'
UNION ALL
SELECT 'Paint' as app,'2018-05-07' as 'Date', 6 as 'Count'
) as A
ANSWER:
DECLARE #SQL VARCHAR(MAX)
DECLARE #Columns VARCHAR(MAX) = ''
DECLARE #Columns2 VARCHAR(MAX) = ''
SELECT #Columns = #Columns + '[' + a.[Column] + '], '
FROM
(SELECT DISTINCT [date] as [Column]
FROM #TEMP) as a
SELECT #Columns2 = #Columns2 + 'ISNULL([' + a.[Column] + '],0) as [' + a.[column] +'], '
FROM
(
SELECT DISTINCT [date] as [Column]
FROM #TEMP
) as a
SET #Columns2 = Left(#Columns2, Len(#Columns2) - 1)
SET #Columns = Left(#Columns, Len(#Columns) - 1)
SET #SQL = 'SELECT app, ' + #Columns2
+ ' FROM #TEMP PIVOT (Avg (Count) FOR Date IN ('
+ #Columns
+ ')) AS pt '
--PRINT #Columns
EXEC( #SQL )

Related

Run query for multiple tables and columns

I have the following code, I need to run this code through over 200 tables, can I create a list and a for loop in the variable to go through all the tables and fetch the results for each? Also, how can I include the table name for each row in this query? I also need to go through a list on the column but I can't figure that out from the table list.
DECLARE #table AS VARCHAR(100)
DECLARE #column as varchar(50)
set #table = 'x'
set #column = 'y'
declare #query as varchar(max)
set #query = ' SELECT CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4)) as date, count(*) as SRC_CNT
FROM ' + #table +
' WHERE ' + #column + ' >= ''2018-01-01'' AND ' + #column + '< ''2021-12-01''
group BY CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4))
order by date;'
exec(#query)
Rather than loop, I would create a dynamic batch, with all the statements you need, which you can then execute. You can use your best friend to debug the statement(s) if needed:
DECLARE #Column sysname = N'y',
#DateFrom date = '20180101',
#DateTo date = '20211201';
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = STRING_AGG(N'SELECT RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7) AS [Date],' + #CRLF +
--N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF + --Uncomment this line if you need it.
N' COUNT(*) AS SRC_CNT' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + #CRLF +
N'WHERE ' + QUOTENAME(#Column) + N' >= #DateFrom AND ' + QUOTENAME(#Column) + N' < #DateTo' + #CRLF +
N'GROUP BY RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7)' + #CRLF +
N'ORDER BY [date];',#CRLF)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE EXISTS (SELECT 1
FROM sys.columns c
WHERE c.object_id = t.object_id
AND c.[name] = #Column);
--PRINT #SQL; --Your best friend
EXEC sys.sp_executesql #SQL, N'#DateFrom date, #DateTo date', #DateFrom, #DateTo;
Given tables and sample data:
CREATE TABLE dbo.x(y date, r int);
CREATE TABLE dbo.y(y date, r int);
CREATE TABLE dbo.z(y date, r int);
INSERT dbo.x(y,r) VALUES('20180105',5),
('20180107',6),('20180509',7);
INSERT dbo.y(y,r) VALUES('20180905',5),
('20181007',6),('20181009',7);
INSERT dbo.z(y,r) VALUES('20180605',5),
('20180607',6),('20180609',7);
This dynamic SQL can be generated:
DECLARE #col sysname = N'y',
#StartDate date = '20180101',
#EndDate date = '20211201';
DECLARE #sql nvarchar(max) = N'',
#base nvarchar(max) = N'SELECT [Table Name] = $tblQ$,
[date] = DATEFROMPARTS(YEAR($col$), MONTH($col$), 1),
SRC_CNT = COUNT(*)
FROM dbo.$tbl$ WHERE $col$ >= #s AND y < #e
GROUP BY YEAR($col$), MONTH($col$)';
SELECT #sql = STRING_AGG(REPLACE(REPLACE(REPLACE
(#base, N'$tblQ$', QUOTENAME(t.name, char(39))
),N'$tbl$',QUOTENAME(t.name)), N'$col$', #col), N'
UNION ALL
') + ' ORDER BY [date];'
FROM sys.tables AS t
WHERE EXISTS (SELECT 1 FROM sys.columns
WHERE [object_id] = t.[object_id]
AND name = #col);
PRINT #sql;
EXEC sys.sp_executesql #sql,
N'#s date, #e date',
#StartDate, #EndDate;
Which produces a query like this (only tables that actually have that column name, could be made even safer by making sure they're using a date/time type):
SELECT [Table Name] = 'x',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[x] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'y',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[y] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'z',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[z] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y) ORDER BY [date];
That generates output like this:
Table Name
date
SRC_CNT
x
2018-01-01
2
x
2018-05-01
1
z
2018-06-01
3
y
2018-09-01
1
y
2018-10-01
2
Example db<>fiddle
If you really want yyyy-MM instead of yyyy-MM-dd on the output, you can just change this line in the declaration of #base:
[date] = CONVERT(char(7), DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 120),
And I realized the requirement was MM-yyyy, in which case:
[date] = RIGHT(CONVERT(char(10),
DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 105), 7),
I usually do the following for tables:
SELECT TABLE_NAME
FROM [<DATABASE_NAME>].INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
You said you could handle columns from there.

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)

is any other way to do in dynamic pivot

while answering one question i got struck with other question in mind.when it is normal pivot it is working fine but if i'm trying to do Dynamic query when the problem arises
after answering he asked for Dynamic Pivot
PIVOT the date column in SQL Server 2012
if OBJECT_ID('tempdb..#temp') is not null
begin
drop table #temp
end
CREATE table #temp (dated varchar(10),E1 int,E2 int,E3 int,E4 int)
insert into #temp
(dated,E1,E2,E3,E4)values
('05-27-15',1,1,2,3),
('05-28-15',2,3,NULL,5),
('05-29-15',3,4,null,2)
DECLARE #statement NVARCHAR(max)
,#columns NVARCHAR(max)
SELECT #columns = ISNULL(#columns + ', ', '') + N'[' + tbl.dated + ']'
FROM (
SELECT DISTINCT dated
FROM #temp
) AS tbl
SELECT #statement = 'Select P.col,MAX('+#columns+') from (
select col,' + #columns + ' from (
select * from #temp
CROSS APPLY(values(''E1'',E1),(''E2'',E2),(''E3'',E3),(''E4'',E4))cs (col,val))PP
PIVOT(MAX(val) for dated IN (' + #columns + ')) as PVT)P
GROUP BY P.COL
'
PRINT #statement
EXEC sp_executesql #statement = #statement
my problem is how can i take MAX() conditions for the all dates dynamically like
max(05-27-15),max(05-28-15) etc dates are coming dynamically how to assign max condition
Moving the MAX aggregate to column list variable will fix the issue
DECLARE #statement NVARCHAR(max),
#columns NVARCHAR(max),
#select_columns NVARCHAR(max)
SELECT #select_columns = Isnull(#select_columns + ', ', '')+ N'MAX([' + tbl.dated + '])'
FROM (SELECT DISTINCT dated
FROM #temp) AS tbl
SELECT #columns = Isnull(#columns + ', ', '') + N'[' + tbl.dated+ ']'
FROM (SELECT DISTINCT dated
FROM #temp) AS tbl
SELECT #statement = 'Select P.col,' + #select_columns
+ ' from (
select col,' + #columns
+ ' from (
select * from #temp
CROSS APPLY(values(''E1'',E1),(''E2'',E2),(''E3'',E3),(''E4'',E4))cs (col,val))PP
PIVOT(MAX(val) for dated IN (' + #columns
+ ')) as PVT)P
GROUP BY P.COL
'
PRINT #statement
EXEC sp_executesql #statement = #statement

Incorrect syntax of dynamic sql query

I need help with body of dynamic sql query.
Now, my query like this
declare #query nvarchar(MAX), #preSubQuery nvarchar(200), #subQueryTxt nvarchar(500), #postSubQuery nvarchar(100);
set #postSubQuery = N' ELSE 0 END))'
set #preSubQuery = N' ,SUM(( CASE DATEDIFF(WEEK, pay.dateCreate, back.dateCreate) WHEN ';
set #subQueryTxt = N' THEN
CASE
WHEN ...
THEN 0
WHEN ...
THEN ...
ELSE
CASE
WHEN ...
THEN ...
WHEN ...
THEN ...
ELSE 0
END
END ';
select #query = N'select DATEPART(ISO_WEEK, pay.dateCreate) backWeek ,SUM(pay_and_back.PayActualPrincipalAmt) AS TotalAmount' +
+ N' #preSubQuery' + N' 0' + ' #subQueryTxt' + ' #postSubQuery'
+ N' #preSubQuery' + N' 1' + ' #subQueryTxt' + ' #postSubQuery'
+ N' #preSubQuery' + N' 2' + ' #subQueryTxt' + ' #postSubQuery'
....
+ N' #preSubQuery' + N' 12' + ' #subQueryTxt' + ' #postSubQuery'
select #query = #query + N'
from FinDocPayToPayback pay_and_back
left join FinDocument pay on pay.id = pay_and_back.PayID
left join FinDocument back on back.id = pay_and_back.PaybackID
group by DATEPART(ISO_WEEK, pay.dateCreate) order by 1';
select #query
EXECUTE sp_executesql #query
,N'#preSubQuery nvarchar(200)', #preSubQuery
,N'#subQueryTxt nvarchar(500)', #subQueryTxt
,N'#postSubQuery nvarchar(100)', #postSubQuery;
But i get error Incorrect syntax. First, I need help with this query. Second,
can anyone give more information about how to construct complex dynamic query?
This is your issue. Somewhere in between .... is there, which cause an issue.
Every time you can use print #query, whenever you stuck or get error.
declare #query nvarchar(MAX), #preSubQuery nvarchar(200), #subQueryTxt nvarchar(500), #postSubQuery nvarchar(100);
set #postSubQuery = N' ELSE 0 END))'
set #preSubQuery = N' ,SUM(( CASE DATEDIFF(WEEK, pay.dateCreate, back.dateCreate) WHEN ';
set #subQueryTxt = N' THEN
CASE
WHEN ...
THEN 0
WHEN ...
THEN ...
ELSE
CASE
WHEN ...
THEN ...
WHEN ...
THEN ...
ELSE 0
END
END ';
select #query = N'select DATEPART(ISO_WEEK, pay.dateCreate) backWeek ,SUM(pay_and_back.PayActualPrincipalAmt) AS TotalAmount' +
+ N' #preSubQuery' + N' 0' + ' #subQueryTxt' + ' #postSubQuery'
+ N' #preSubQuery' + N' 1' + ' #subQueryTxt' + ' #postSubQuery'
+ N' #preSubQuery' + N' 2' + ' #subQueryTxt' + ' #postSubQuery'
+ N' #preSubQuery' + N' 12' + ' #subQueryTxt' + ' #postSubQuery'
select #query = #query + N'
from FinDocPayToPayback pay_and_back
left join FinDocument pay on pay.id = pay_and_back.PayID
left join FinDocument back on back.id = pay_and_back.PaybackID
group by DATEPART(ISO_WEEK, pay.dateCreate) order by 1';
select #query
This is actual output of your above query at execution time
SELECT Datepart(iso_week, pay.datecreate) backweek ,
Sum(pay_and_back.payactualprincipalamt) AS totalamount #preSubQuery 0 #subQueryTxt #postSubQuery #preSubQuery 1 #subQueryTxt #postSubQuery #preSubQuery 2 #subQueryTxt #postSubQuery #preSubQuery 12 #subQueryTxt #postSubQuery
FROM findocpaytopayback pay_and_back
LEFT JOIN findocument pay
ON pay.id = pay_and_back.payid
LEFT JOIN findocument back
ON back.id = pay_and_back.paybackid
GROUP BY datepart(iso_week, pay.datecreate)
ORDER BY 1
For complex dynamic query, this is example to understand.
Example1
--declare table1 table ( id int, value varchar(10) )
--drop table Table1
begin tran
create table table1 ( id int, value varchar(10) )
insert into table1 values( 1,'001')
insert into table1 values(2, '002')
insert into table1 values( 3,'003')
insert into table1 values( 4,'004')
create table table2 ( id int, value varchar(10) )
insert into table2 values( 1,'t2value1')
insert into table2 values(2, 't2value2')
insert into table2 values( 3,'t2value3')
insert into table2 values( 4,'t2value4')
-----1st example
declare #sql nvarchar(max) , #temp varchar(50) = '1,2,3'
select * from table1
set #sql = 'select * from table1 where id in ( ' + #temp + ')'
declare #join_table varchar(max)= 'table2', #whereCondition varchar(max) = ' where ' , #orderby varchar(max) = ' t1.id'
select #sql
exec sp_executesql #sql
-----2nd example
set #sql = 'select * from table1 t1 inner join ' + #join_table + ' t2 on t1.id = t2.id '
--if #parameter != null you can give above this condition for
set #whereCondition = #whereCondition + ' t1.id in ( ' + #temp + ')'
--if #orderby != null order by condition
set #orderby = ' order by '+ #orderby
set #sql = #sql + #whereCondition + #orderby
select #sql --you can check that what is wrong by
exec sp_executesql #sql
rollback
**Example2 : Passing parameter into dynamic query **
--begin tran
--create table table1 ( id int, value varchar(10) )
--insert into table1 values( 1,'001')
--insert into table1 values(2, '002')
--insert into table1 values( 3,'003')
--insert into table1 values( 4,'004')
declare #sql nvarchar(max) , #temp nvarchar(50) = '1,2,3', #tempIntSingleValue nvarchar(50) = '2'
select * from table1
set #sql = 'select * from table1 where id in ( ' + #temp + ')'
print #sql
exec sp_executesql #sql
set #sql = 'select * from table1 where id in ( #tempInner)'
print #sql
exec sp_executesql #sql , N'#tempInner int', #tempInner = #tempIntSingleValue
--rollback

PIVOT and TEMP TABLE

please can you help me put this script into a temp table. i have been working on this for hours and it keeps giving me errors.
DECLARE #PivotColumnHeaders VARCHAR(MAX)
SELECT #PivotColumnHeaders =
COALESCE(#PivotColumnHeaders + ',[' + CAST(expansion as varchar(max)) + ']',
'[' + CAST(expansio
n as varchar(max))+ ']')
INTO #temp
FROM (Select distinct expansion from #CD4_VL2) results
DECLARE #PivotTableSQL NVARCHAR(MAX)
SET #PivotTableSQL = N'
select * from
(
select subjectID,expansion,printableValue1
from #CD4_VL2) as results
PIVOT
(MAX([printableValue1])
FOR [expansion] IN (
' + #PivotColumnHeaders + '
)
) as PivotTable
--order by Performed_Date_And_Time desc
'
Execute(#PivotTableSQL )
Because you've pivoted on the expansion columns, the respective values of MAX(printableValue1) will now be in columns with the names of the data in the Expansion column. Change this like so:
DECLARE #PivotColumnHeaders NVARCHAR(MAX) = 'Expansion1, Expansion2, Expansion3';
DECLARE #PivotTableSQL NVARCHAR(MAX);
SET #PivotTableSQL =
N'select subjectID, ' + #PivotColumnHeaders
+' from #CD4_VL2
PIVOT
(
MAX([printableValue1])
FOR [expansion] IN (' + #PivotColumnHeaders + ')) x';
Execute(#PivotTableSQL);
SqlFiddle here