Problem
Pivoting (or producing a “cross-tab”) is a common reporting requirement – data is stored in columns and you need to present it in rows. This was a nice feature that was added to SQL Server, but you don’t always know all of the values you need to pivot on. In this tip we look at how you can dynamically create the pivot command to handle these unknown values.
Solution
In these cases it may make sense to construct a dynamic pivot. Let’s assume the following simple schema:
USE tempdb;
GO
CREATE TABLE dbo.Products
(
ProductID INT PRIMARY KEY,
Name NVARCHAR(255) NOT NULL UNIQUE
/* other columns */
);
INSERT dbo.Products VALUES
(1, N'foo'),
(2, N'bar'),
(3, N'kin');
CREATE TABLE dbo.OrderDetails
(
OrderID INT,
ProductID INT NOT NULL
FOREIGN KEY REFERENCES dbo.Products(ProductID),
Quantity INT
/* other columns */
);
INSERT dbo.OrderDetails VALUES
(1, 1, 1),
(1, 2, 2),
(2, 1, 1),
(3, 3, 1);
A query to obtain each product’s total quantity ordered would look something like this (ignoring details about order date, etc.):
SELECT p.Name, Quantity = SUM(o.Quantity)
FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
GROUP BY p.Name;
And in this case the results would look like this:

But what if the results needed to look like this?

We could write a hard-coded PIVOT query, but only if we know all of the potential product names:
SELECT p.[foo], p.[bar], p.[kin]
FROM
(
SELECT p.Name, o.Quantity
FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
) AS j
PIVOT
(
SUM(Quantity) FOR Name IN ([foo],[bar],[kin])
) AS p;
If we add a new product, this PIVOT query no longer gives us the complete story. And we certainly don’t want to have to go back and edit these queries every time we add a product (never mind when a product’s name is changed). So consider some new data:
INSERT dbo.Products SELECT 4, N'blat';
INSERT dbo.OrderDetails SELECT 4,4,5;
Obviously the hard-coded PIVOT query is not going to pick up this new product:
SELECT p.[foo], p.[bar], p.[kin]
FROM
(
SELECT p.Name, o.Quantity
FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
) AS j
PIVOT
(
SUM(Quantity) FOR Name IN ([foo],[bar],[kin])
) AS p;
Results are the same as before:

Enter dynamic SQL. Typically frowned upon, this is one of the scenarios where you are likely to make a strong case for its use. We can simply build the output / pivot column list at runtime, and append it to the rest of the query:
DECLARE @columns NVARCHAR(MAX), @sql NVARCHAR(MAX);
SET @columns = N'';
SELECT @columns += N', p.' + QUOTENAME(Name)
FROM (SELECT p.Name FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
GROUP BY p.Name) AS x;
SET @sql = N'
SELECT ' + STUFF(@columns, 1, 2, '') + '
FROM
(
SELECT p.Name, o.Quantity
FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
) AS j
PIVOT
(
SUM(Quantity) FOR Name IN ('
+ STUFF(REPLACE(@columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT @sql;
EXEC sp_executesql @sql;
Results:
SELECT p.[foo], p.[bar], p.[kin], p.[blat]
FROM
(
SELECT p.Name, o.Quantity
FROM dbo.Products AS p
INNER JOIN dbo.OrderDetails AS o
ON p.ProductID = o.ProductID
) AS j
PIVOT
(
SUM(Quantity) FOR Name IN ([foo],[bar],[kin],[blat])
) AS p;

This is a fairly trivial example, but I hope it demonstrates one approach you could use to achieve dynamic PIVOT.
Next Steps
- Identify queries where you are currently hard-coding PIVOT output columns and evaluate whether converting them to dynamic PIVOT may make sense
- Read the following tips and other resources:

Aaron Bertrand (@AaronBertrand) is a passionate technologist with industry experience dating back to Classic ASP and SQL Server 6.5. He also blogs at sqlblog.org.
- MSSQLTips Awards: Author of the Year – 2016, 2023 | Leadership (200+ tips) – 2022



Hey Aaron,
that some great kind of explanation. Worked nearly perfekt for me. The only thing I could not fix.
In my case I reduce the values on categories per customer, and for some joins later I need the customer id.
So the customer ID is included in query before the pivot function, but doesn’t appear later.
If I run the pivot without any flexible strukture, I get the customer ID. So may have you or any of the other readers some suggestions for me.
Thanks in advance
Thomas
Here is what i manage to make after long time ot procedure using for this
Adventureworks DB
in table for pivot you place your data for pivot
DECLARE @SQL NVARCHAR(MAX);
SELECT FORPIVOT.[ROWSDATA],FORPIVOT.[COLLNAME],FORPIVOT.[VALUE] INTO #TEMPTABLE FROM
(– DATA FOR PIVOTING , ДАННИ ЗА ПИВОТ ТАБЛИЦА
SELECT
YEAR(SOD.MODIFIEDDATE)AS ROWSDATA, — ROWS ,РЕДОВЕ
PP.NAME AS COLLNAME, –COLLUMN NAMES, ИМЕНА НА КОЛОНИ
SUM(SOD.ORDERQTY )AS [VALUE] — VALUES FOR CALCULATE, ИЗЧИСЛЕНА СТОЙНОС
FROM SALES.SALESORDERDETAIL SOD
JOIN PRODUCTION.PRODUCT PP ON SOD.PRODUCTID =PP.PRODUCTID
GROUP BY YEAR(SOD.MODIFIEDDATE),PP.NAME
)AS FORPIVOT;
–DO NOT MODIFY ANYTHING ELSE, ДРУГО НЕ СЕ ПИПА
SELECT DISTINCT COLLNAME AS COLLNAME INTO #TEMPCOLLS FROM #TEMPTABLE;
SELECT @SQL=(SELECT ‘
WITH [TABLE] AS
(SELECT [ROWSDATA],[COLLNAME],[VALUE] FROM #TEMPTABLE)
SELECT * FROM
(SELECT [ROWSDATA],[COLLNAME],[VALUE] FROM #TEMPTABLE ) TOTAL
PIVOT
(SUM(VALUE) FOR [COLLNAME] IN
(‘+(SELECT LEFT (STUFF((SELECT QUOTENAME(COLLNAME)+’,’ FROM #TEMPCOLLS FOR XML PATH(”)), 1, 0, ”),
LEN(STUFF((SELECT DISTINCT QUOTENAME( COLLNAME)+’,’ FROM #TEMPCOLLS FOR XML PATH(”)), 1, 0, ”))-1) )+’)) AS PIVOTTABLE
ORDER BY [ROWSDATA]’)
EXEC SP_EXECUTESQL @SQL
DROP TABLE #TEMPTABLE
DROP TABLE #TEMPCOLLS