'T-SQL Pivot table rows into dynamic columns per count of instances
Given a table thus:
| ID | Ingredient | Brand | KitchenLocation | PurchaseSize | PurchaseUnit | InStockA | InStockB |
|---|---|---|---|---|---|---|---|
| 14 | Beans, Pinto | Chefs Quality | Pantry | 55 | oz | 15 | 60 |
| 15 | Beans, Pinto | Chefs Quality | Pantry | 108 | oz | 2 | 7 |
| 16 | Beans, Pinto | Chefs Quality | Pantry | 648 | oz | 1 | 5 |
| 17 | Beans, Pinto | First Street | Pantry | 15 | oz | 23 | 10 |
| 18 | Beans, Pinto | First Street | Pantry | 40 | oz | 5 | 1 |
| 19 | Beans, Pinto | Sun Vista | Pantry | 29 | oz | 0 | 100 |
I am hoping to find a way of pivoting so the output is grouped by Ingredient, Brand, and KitchenLocation. So the PurchaseSize, PurchaseUnit, InStockA, and InStockB values display as dynamic columns inside this grouping:
| Ingredient | Brand | KitchenLocation | Size1 | Unit1 | InStock1A | InStock1B | Size2 | Unit2 | InStock2A | InStock2B | Size3 | Unit3 | InStock3A | InStock3B |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Beans, Pinto | Chefs Quality | Pantry | 55 | oz | 15 | 60 | 108 | oz | 2 | 7 | 648 | oz | 1 | 5 |
| Beans, Pinto | First Street | Pantry | 15 | oz | 23 | 10 | 40 | oz | 5 | 1 | ||||
| Beans, Pinto | Sun Vista | Pantry | 29 | oz | 0 | 100 |
I can't find an exact question here on Stackoverflow, though of course some are similar. I started to put this code together, but I am stuck, firstly, because Ingredient comes back as NULL in all cases, and secondly I don't know how to dynamically get the new rows to display with a calculated number in the column name (eg. InStock1A, InStock2A etc.):
SELECT Ingredient, Brand, KitchenLocation, InStockA, InStockB
FROM
(SELECT Ingredient AS EIngredient, Brand, KitchenLocation, PurchaseSize, InStockA, InStockB
FROM tmpHoldingTable) AS Source_Table
PIVOT
(MAX(PurchaseSize)
FOR
EIngredient IN (Ingredient)
) AS PIVOT_TABLE
Can anyone point me in the right direction please? Many thanks in advance.
Solution 1:[1]
Many thanks to @Larnu for pointing me towards something I could adapt. The answer is thus:
DECLARE @qry nvarchar(MAX),
@crlf nchar(2) = NCHAR(13) + NCHAR(10),
@maxTally int;
SELECT @maxTally = MAX(C)
FROM (SELECT COUNT(*) AS C
FROM tmpHoldingTable
GROUP BY Ingredient, Brand, KitchenLocation) R;
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (@maxTally) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --100 rows, add more Ns for more rows
SELECT @qry = N'WITH RNs AS(' + @crlf +
N' SELECT Ingredient,' + @crlf +
N' Brand,' + @crlf +
N' KitchenLocation,' + @crlf +
N' PurchaseSize,' + @crlf +
N' PurchaseUnit,' + @crlf +
N' InStockA,' + @crlf +
N' InStockB,' + @crlf +
N' ROW_NUMBER() OVER (PARTITION BY Ingredient, Brand, KitchenLocation ORDER BY (SELECT ID)) AS RN ' + @crlf +
N' FROM tmpHoldingTable)' + @crlf +
N'SELECT Ingredient,' + @crlf +
N' Brand,' + @crlf +
N' KitchenLocation,' + @crlf +
STUFF((SELECT N',' + @crlf +
CONCAT(N' MAX(CASE RN WHEN ',T.I,N' THEN PurchaseSize END) AS PurchaseSize',T.I)
FROM Tally T
ORDER BY T.I ASC
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,3,N'') + ',' + @crlf +
STUFF((SELECT N',' + @crlf +
CONCAT(N' MAX(CASE RN WHEN ',T.I,N' THEN PurchaseUnit END) AS PurchaseUnit',T.I)
FROM Tally T
ORDER BY T.I ASC
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,3,N'') + ',' + @crlf +
STUFF((SELECT N',' + @crlf +
CONCAT(N' MAX(CASE RN WHEN ',T.I,N' THEN InStockA END) AS InStockA',T.I)
FROM Tally T
ORDER BY T.I ASC
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,3,N'') + ',' + @crlf +
STUFF((SELECT N',' + @crlf +
CONCAT(N' MAX(CASE RN WHEN ',T.I,N' THEN InStockB END) AS InStockB',T.I)
FROM Tally T
ORDER BY T.I ASC
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,3,N'') + @crlf +
N'FROM RNs R' + @crlf +
N'GROUP BY Ingredient,' + @crlf +
N' Brand, ' + @crlf +
N' KitchenLocation;';
EXEC sys.sp_executesql @qry;
We put all the fields we need in the RNs WITH statement, making sure to group down by the three grouping fields. We then use the STUFF function on each of the columns that we want to dynamically display. The only downside is that the columns come out not in the exact desired order, but that can be fixed by placing the output into a separate holding table and then utilising a SELECT from this where the columns are placed in the correct order and returned. Many thanks @Larnu!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Davy C |
