How to properly create a Dynamic Pivot Function in SQL? [duplicate] - sql

This question already has answers here:
Group by column and multiple Rows into One Row multiple columns
(2 answers)
Closed 8 months ago.
I come back with my problem. I've been trying to create a dynamic pivot procedure but I have some trouble while creating the columns.
Example:
TranID
Bank Name
NetAmount
Account
01
StormWindBank
23.0$
A
02
StormWindBank
14.0$
B
03
StormWindBank
00.0$
A
04
StormWindBank
12.0$
B
Result intended :
Account
Bank Name
NetAmount
NetAmount2
A
StormWindBank
23.0$
00.0$
B
StormWindBank
14.0$
12.0$
SELECT DISTINCT [Account]
,[Currency]
,TranID
,[Bank Code]
,[Bank Name]
,[Client No_]
,[NetAmount]
INTO #TEMP
FROM [My Table]
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.NetAmount)
FROM #TEMP c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Account], [Bank Name], ' + #cols + ' from
(
select [Account],TranID, NetAmount, [Bank Name]
from #TEMP
) x
pivot
(
max(TranID)
for NetAmount in (' + #cols + ')
) p '
So far, I have the result below:
Account
Bank Name
23.0$
00.0$
A
StormWindBank
1
2
B
StormWindBank
3
4
The 1, 2, 3, 4 are the TranID.
I'm close but I have been struggling to fix that, anyone have an idea ?
Thank you!

You need to PIVOT off a derived column using row_number()
Example
Declare #SQL varchar(max) = stuff( ( Select Distinct concat(',[NetAmount',row_number() over (partition by [Bank Name],[Account] order by [TranID]),']' )
From #TEMP For XML Path('') ),1,1,'')
Set #SQL = '
Select *
From (
Select [Account]
,[Bank Name]
,Item = concat(''NetAmount'',row_number() over (partition by [Bank Name],[Account] order by [TranID]) )
,Value = [NetAmount]
from #TEMP
) src
Pivot ( max( [Value] ) for Item in ('+ #SQL +') ) pvt
'
Exec(#SQL)
Results
Account Bank Name NetAmount1 NetAmount2
A StormWindBank 23.0$ 00.0$
B StormWindBank 14.0$ 12.0$

Related

SQL PIVOT to generate required output

Please see the data below:
I am looking for a query that generates the following output:
I am experimenting with 'PIVOT', but have not yet achieved the desired outcome.
This should work:
SELECT ReviewType, DER, LEI, NOR, [NOT], LIN
FROM Src
PIVOT (SUM(Total) FOR OwningAgency IN (DER, LEI, NOR, [NOT], LIN)) P
Simple pivoting:
SELECT *
FROM YourTable
PIVOT (
MAX(Total) FOR OwningAgency IN ([DER],[LEI],[NOR],[NOT],[LIN])
) pvt
Another way:
SELECT ReviewType,
CASE WHEN OwningAgency = 'DER' THEN MAX(Total) END [DER],
CASE WHEN OwningAgency = 'LEI' THEN MAX(Total) END [LEI],
CASE WHEN OwningAgency = 'NOR' THEN MAX(Total) END [NOR],
CASE WHEN OwningAgency = 'NOT' THEN MAX(Total) END [NOT],
CASE WHEN OwningAgency = 'LIN' THEN MAX(Total) END [LIN]
FROM YourTable
GROUP BY ReviewType,OwningAgency
Use dynamic column collection to select PIVOT Data, because it gives you any new column value added in table, suppose after 2-3 days if new OwningAgency say for XYZ added in your table even that it show your new column in PIVOT result:
CREATE TABLE tblOwningAgency
(
OwningAgency VARCHAR(50),
ReviewType CHAR(1),
Total INT
)
INSERT INTO tblOwningAgency VALUES('DER','E',584)
,('LEI','S',84)
,('NOR','S',28148)
,('LIN','S',1261)
,('DER','T',6310)
,('NOR','T',5527)
,('NOT','T',35705)
,('LIN','E',606)
,('NOT','S',22978)
,('LEI','T',4283)
,('LIN','T',687)
,('LEI','E',431)
,('NOR','E',161)
,('NOT','E',842)
,('DER','S',1937)
,('XYZ','S',1937)
DECLARE #OwningAgency AS NVARCHAR(MAX),#Query AS NVARCHAR(MAX);
SET #OwningAgency = STUFF((SELECT distinct ',' + QUOTENAME(OwningAgency)
FROM tblOwningAgency c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #Query =
'SELECT ReviewType, ' + #OwningAgency + ' from
(
SELECT *
FROM tblOwningAgency
) x
pivot
(
SUM(Total)
FOR OwningAgency in (' + #OwningAgency + ')
) p '
EXECUTE(#query)
#OwningAgency : It will give you your column list on which you wants to apply SUM

sql query, each column in result is a different WHERE clause [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 6 years ago.
I'm trying to find the easiest way to return some results based on dates.
So for example the table is like this
product date qty
corn 4/18/16 5
corn 4/18/16 1
corn 4/15/16 10
I would want the results to be
product 4/15/16 4/18/16
corn 10 6
So I am grouping by the product, sum the qty, but each column in result will be a different WHERE filedate = .
Can someone please point me in the right direction?
Try like this
SELECT * FROM
(
SELECT product,date,qty
FROM Table1
)X
PIVOT
(
SUM(X.qty)
FOR date IN([4/18/16],[4/15/16])
)Y
For multiple source, you need to write dynamic query like below
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
;WITH CTE (DATELIST, MAXDATE) AS
(
SELECT '01/04/2016' DATELIST, '30/04/2016' MAXDATE
UNION ALL
SELECT DATEADD(dd, 1, DATELIST), MAXDATE
FROM CTE
WHERE DATELIST < MAXDATE
)
SELECT C.DATELIST
INTO #TempDates
FROM CTE C
SELECT #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(convert(CHAR(10), DATELIST, 120))
FROM #TempDates
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT product,' + #cols + ' FROM
(
SELECT B.product, B.date, B.qty, D.DATELIST, CONVERT(CHAR(10), DATELIST, 120) PivotDate
FROM #TempDates D
LEFT JOIN YourTable B ON D.DATELIST BETWEEN ''01/04/2016'' AND ''30/04/2016''
) X
PIVOT
(
SUM(qty)
FOR PivotDate IN (' + #cols + ')
) P '
EXECUTE(#query)

Pivot SQL table with dynamic year columns

I'm having trouble figuring this out. I've checked similar posts but they only have one column as pivoted as a row. While I need to pivot
I have the following query:
SELECT
Year([Date]) as Year
,SUM([Drop]) as [Drop]
,SUM([TicketsDistributed]) as [TicketsDistributed]
,SUM([TicketsSold]) as [TicketsSold]
,SUM([GrossTickets]) as [GrossTickets]
,SUM([GrossTickets])/SUM(TicketsSold) as 'Per Cap'
FROM [dbo].[Tickets]
group by [Date]
Which give me this result:
Year Drop TicketsDistributed TicketsSold GrossTickets Per Cap
2016 222 100 5000 4000.00 0.800000
2015 222 110 5000 4000.00 0.900000
2014 222 120 5000 4000.00 1.00000
And I would like the following:
2016 2015 2014
Drop 222 222 222
TicketsDistributed 100 110 120
TicketsSold 5000 5000 5000
GrossTickets 4000 4000 4000
Per Cap 0.8 0.9 1
Based on the suggested answer this is what I have so far but it's not working
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Year(Date))
from [dbo].[SpringTrainings] t
cross apply
(
select SUM([Drop]) as [Drop]
,SUM([TicketsDistributed]) as [TicketsDistributed]
,SUM([TicketsSold]) as [TicketsSold]
,SUM([GrossTickets]) as [GrossTickets]
,SUM([GrossTickets])/SUM(TicketsSold) as PerCap
FROM [dbo].[SpringTrainings]
) c ([Drop],[TicketsDistributed],[TicketsSold],[GrossTickets],PerCap)
group by Year(Date)
order by Year(Date)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Drop],[TicketsDistributed],[TicketsSold],[GrossTickets],PerCap,' + #cols + '
from
(
select SUM([Drop]) as [Drop]
,SUM([TicketsDistributed]) as [TicketsDistributed]
,SUM([TicketsSold]) as [TicketsSold]
,SUM([GrossTickets]) as [GrossTickets]
,SUM([GrossTickets])/SUM(TicketsSold) as PerCap
FROM [dbo].[SpringTrainings]
) x
pivot
(
max(Year([Date]))
for Year([Date]) in (' + #cols + ')
) p '
execute sp_executesql #query;
If you wish to keep using the pivot operator within T-SQL, then first you need to "unpivot" your existing query so you have Year, Label, and Value. While there is an unpivot operator in T-SQL personally I find using CROSS APPLY and VALUES to be much simpler and equally as fast (for more on his approach read this article by Brad Schultz), I particularly like it because I can visualize the result easily by the way I layout the value pairs.
SELECT
d.Year
, a.label
, a.value
FROM (
SELECT
YEAR([Date]) AS [Year]
, SUM([Drop]) AS [Drop]
, SUM([TicketsDistributed]) AS [TicketsDistributed]
, SUM([TicketsSold]) AS [TicketsSold]
, SUM([GrossTickets]) AS [GrossTickets]
, SUM([GrossTickets]) / SUM(TicketsSold) AS [PerCap]
FROM [dbo].[Tickets]
GROUP BY
[Year]
) AS t
CROSS APPLY ( /* now transform into 5 rows per year but just 1 value column */
VALUES
('Drop',t.Drop)
, ('TicketsDistributed',t.TicketsDistributed)
, ('TicketsSold',t.TicketsSold)
, ('GrossTickets',t.GrossTickets)
, ('PerCap',t.PerCap)
) AS a (label, value)
That query (above) replaces the derived table x in your dynamic SQL. Once the data has been massaged into that form the pivot looks way simpler:
) x
pivot
(
max([x.Value])
for [x.Year] in ([2014],[2015],[2016])
) p
For your #cols I would suggest something simple like this:
SELECT DISTINCT
QUOTENAME(Year([date]))
FROM [dbo].[Tickets]
TIP: if you need a way to order the rows, include that in the cross apply too, like this:
CROSS APPLY ( /* now transform into 5 rows per year but just 1 value column */
VALUES
(1, 'Drop',t.Drop)
, (2, 'TicketsDistributed',t.TicketsDistributed)
, (3, 'TicketsSold',t.TicketsSold)
, (4, 'GrossTickets',t.GrossTickets)
, (5, 'PerCap',t.PerCap)
) AS a (row_order, label, value)
and then that [row_order] can be used after the pivot is performed.
So the overall could look like this:
DECLARE #cols AS nvarchar(max)
, #query AS nvarchar(max)
SELECT #cols = STUFF((
SELECT DISTINCT
',' + QUOTENAME(YEAR([date]))
FROM [dbo].[Tickets]
FOR xml PATH (''), TYPE
)
.value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
SET #query = 'SELECT [Year], [Label], '
+ #cols
+ ' FROM (
SELECT
d.Year
, a.label
, a.value
FROM (
SELECT
YEAR([Date]) AS [Year]
, SUM([Drop]) AS [Drop]
, SUM([TicketsDistributed]) AS [TicketsDistributed]
, SUM([TicketsSold]) AS [TicketsSold]
, SUM([GrossTickets]) AS [GrossTickets]
, SUM([GrossTickets]) / SUM(TicketsSold) AS [PerCap]
FROM [dbo].[Tickets]
GROUP BY
[Year]
) AS d
CROSS APPLY (
VALUES
(1,''Drop'',t.Drop)
, (2,''TicketsDistributed'',t.TicketsDistributed)
, (3,''TicketsSold'',t.TicketsSold)
, (4,''GrossTickets'',t.GrossTickets)
, (5,''PerCap'',t.PerCap)
) AS a (row_order,label,value)
) x
pivot
(
max([x.Value])
for [x.Year] in (' + #cols + ')
) p
ORDER BY [row_order]'
EXECUTE sp_executesql #query;

Dynamic pivot table with two id columns

This is probably simple but I"m just not seeing it. Your help is appreciated. I have a table in MS SQLServer that looks like this
CustomerID Time ItemID
1 2008-10-07 06:32:53:00.000 87432
1 2008-10-07 06:32:53:00.000 26413
2 2010-06-23 03:45:10:00.000 6312
2 2011-09-14 07:36:03:00.000 87432
2 2011-09-14 07:36:03:00.000 87432
I want to end up with a table that has each customer, the timestamp and the count of the items purchased during that timestamp, that looks like this
CustomerID Time 87432 26413 6312
1 2008-10-07 06:32:53:00.000 1 1 0
2 2010-06-23 03:45:10:00.000 0 0 1
2 2011-09-14 07:36:03:00.000 2 0 0
In the source table, the time and itemID are variable (and plentiful), so I'm thinking a dynamic pivot will do the trick. Is this possible to do with pivot? If so, how?
You can do this with a dynamic PIVOT. This will count the number of ItemIds that you have for any number of Times.
See a SQL Fiddle with a Demo. This demo leaves the time as a varchar as you stated they were. But this will work if the data is a datetime as well.
Since you want time in the final result, then when you select the columns, you will need to add the time column twice. I called it time1 and time. This allows you to aggregate on time1 in the PIVOT and still have a time column for your final product.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(itemid)
from temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT customerid, [time], ' + #cols + ' from
(
select customerid, [time] as time1, [time] as [time], itemid
from temp
) x
pivot
(
count([time1])
for itemid in (' + #cols + ')
) p '
execute(#query)
Approach #1 - Click here to see Demo
Declare #ItemIDs varchar(1000) = ''
Declare #Query varchar(8000) = ''
Select #ItemIDs = ISNULL(QuoteName(Convert(varchar, ItemID)) + ',', '')
+ #ItemIDs
From
(
Select distinct ItemID From #MyTable
)K
SET #ItemIDs = SUBSTRING(#ItemIDs,0,len(#ItemIDs))
SET #Query = 'Select CustomerID, [Time],' +
#ItemIDs + ' From
(
Select CustomerID, [Time], ItemID from #MyTable
)K Pivot
(
count(ItemID) FOR ItemID IN (' + #ItemIDs + ')
) AS pvt'
EXEC(#Query)
Approach #2 - Click here to see Demo
Select CustomerID, [Time], [87432] as [87432],
[26413] as [26413], [6312] as [6312] From
(
Select CustomerID, [Time], ItemID from #MyTable
)K Pivot
(
count(ItemID) FOR ItemID IN ([87432] , [26413],[6312])
) AS pvt

SQL Pivot table returning NULL for non-existent child table values

I have a typical RDMS setup where records in a main table can have optional records in a related table via a M2M join. I'm trying to PIVOT this data but in cases where there is no relation I want to return a default value. The join I have below is returning NULL.
select *
from
(
SELECT s.Biz_Name, la.Name AS Association, ISNULL(i.Location, 'Default') as Location
FROM dbo.ShopAssociations sa
INNER JOIN dbo.LookupAssociations la
ON sa.AssociationID = la.AssociationID
RIGHT JOIN dbo.Basic_Shop_Info s
ON sa.ShopID = s.ShopID
INNER JOIN dbo.Images i
ON la.ImageID = i.ImageID
) DataTable
PIVOT
(
min(Location)
for association in
([OnCall],[OCGuy],[ASCLogo],[ASC_OtherSt],[ASE],[AASP],[AASP_PA],
[ASE_BlueSeal],[AAA],[AAA-B],[ASA],[ATRA],[ICAR],[CAA],[ACDelco],
[Cert],[ASC],[BBB],[Goodyear],[Limos],[RVs],[Bosch],[NARSA],
[DiscTire],[BigO],[Tires],[Firestone],[ASCCA],[JustTires],[ASE_Blue])
) PivotTable
The output looks like this:
BizName OnCall OCGuy ASCLogo ASC_OtherSt ASE ...
"Wonderful Biz" somevalue somevalue NULL somevalue NULL
What I am trying to achieve is if a child record doesn't exist in INNER JOIN from Basic_Shop_Info to ShopAssociations that we get "Default" instead of NULL. I've tried ISNULL(), Coalesce() and even a CASE statement, all with the same results.
Based on your comment it sounds like you found a solution. I am only answering this to provide a suggestion based on the fact you are pivoting so many columns and they are all hard-coded. You can use dynamic SQL for a PIVOT and your query would look something like this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Name)
from dbo.LookupAssociations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsPivot = STUFF((SELECT distinct ', IsNull(' + QUOTENAME(Name) +', ''Default'')'
from dbo.LookupAssociations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Bizname, ' + #colsPivot + ' from
(
SELECT s.Biz_Name, la.Name AS Association, ISNULL(i.Location, ''Default'') as Location
FROM dbo.ShopAssociations sa
INNER JOIN dbo.LookupAssociations la
ON sa.AssociationID = la.AssociationID
RIGHT JOIN dbo.Basic_Shop_Info s
ON sa.ShopID = s.ShopID
INNER JOIN dbo.Images i
ON la.ImageID = i.ImageID
) x
pivot
(
min(Location)
for association in (' + #cols + ')
) p
'
execute(#query)
The value #colsPivot is adding the IsNull() around each of you columns so you can put in place the Default value. But this should provide the same result as your original query where everything was hard-coded.
This will get the list of columns at run-time so then you do not have to hard-code anything and it will accept new values without having to change the query.
I got this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(c.col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by person_nbr
order by person_nbr,first_name, last_name, medication_name) rn
from TA_PIVOT
) d
cross apply
(
select 'diag' col, 1 sort
) c
group by col, rn, sort
order by rn, sort
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT person_nbr, first_name, last_name,medication_name,' + #cols + '
from
(
select person_nbr,first_name,last_name,medication_name,
col+cast(rn as varchar(10)) col,
value
from
(
-- when you perform an unpivot the datatypes have to be the same.
-- you might have to cast the datatypes in this query
select person_nbr,first_name,last_name, medication_name, cast(icd_code_id as varchar(500)) diag,
row_number() over(partition by person_nbr order by person_nbr, first_name, last_name,medication_name) rn
from ta_pivot
) src
unpivot
(
value
for col in (diag)
) unpiv
) d
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);