In a prior blog post (Standard T-SQL PIVOT) I showed how we can use a PIVOT query to take distinct values in a column and make those values new columns. However, data changes and new columns may be needed. Looking at the data from the prior post, we only had three types of candy bars. What happens when a new candy bar shows up in our transactions? Well, we could update our query to use the new column value. If another new candy bar shows up we could update the query again. This process could go on and on and on. I am going to propose an alternative: using a dynamic PIVOT query. In this blog post I am going to show how we can use dynamic SQL to implement a dynamic PIVOT query.
Let’s start out by looking at the original data:
CustomerID | ProductName | Quantity |
---|---|---|
1 | Snickers | 1 |
1 | Butterfinger | 1 |
1 | Rolo | 2 |
1 | Snickers | 1 |
1 | Butterfinger | 1 |
2 | Rolo | 2 |
After using a PIVOT query we will see the data as described in the table below:
CustomerIDButterfingerRoloSnickers
CustomerID | Butterfinger | Rolo | Snickers |
---|---|---|---|
1 | 2 | 2 | 2 |
2 | NULL | 2 | NULL |
Now update the original data with a few new products:
CustomerID | ProductName | Quantity |
---|---|---|
1 | Snickers | 1 |
1 | Butterfinger | 1 |
1 | Rolo | 2 |
1 | Snickers | 1 |
1 | Butterfinger | 1 |
2 | Rolo | 2 |
1 | MilkyWay | 3 |
2 | Twix | 2 |
If we apply that same query from the prior blog post, listed below, we see the same results as before.
SELECT [CustomerID], [Butterfinger], [Milkyway], [Rolo], [Snicker] FROM #CustomerProducts P PIVOT ( SUM(Quantity) FOR P.ProductName IN ( [Butterfinger], [Milkyway], [Rolo], [Snicker]) ) AS PVT |
CustomerID | Butterfinger | Rolo | Snickers |
---|---|---|---|
1 | 2 | 2 | 2 |
2 | NULL | 2 | NULL |
But what we really wanted was
CustomerID | Butterfinger | Milkyway | Rolo | Snickers | Twix |
---|---|---|---|---|---|
1 | 2 | 3 | 2 | 2 | NULL |
2 | NULL | NULL | 2 | NULL | 2 |
As I mentioned before, we could simply update our query and this blog post is over. However, we are going to push on and create our dynamic version. Let’s take a look at creating the dynamic portion of the PIVOT query. The key part of the dynamic query is the distinct column list. Getting the distinct columns is just a simple DISTINCT query. To get the comma separated column list we will employ a trick that involves a COALESCE or ISNULL statement.
SELECT @PivotList = COALESCE(@PivotList + ',', '') |
Once we have the distinct comma separated list, we can start to build out the dynamic query. We will create an nvarchar variable that we will use to store our dynamic SQL statement, and then we will execute that statement with the sp_executesql function.
Looking at our PIVOT query again, what we will want to do is replace the distinct columns with our comma separated list of values. Then we will take and store the new query into our variable
SELECT [CustomerID],
[Butterfinger], [Milkyway], [Rolo], [Snicker]
FROM #CustomerProducts P
PIVOT ( SUM(Quantity) FOR P.ProductName IN (
[Butterfinger], [Milkyway], [Rolo], [Snicker])
) AS PVT
Converts to this
SET @Output = 'SELECT [CustomerID] , ' + @PivotList + ' FROM #CustomerProducts P PIVOT ( SUM(Quantity) FOR P.ProductName IN (' + @PivotList + ') ) AS PVT' |
Now all that is left is to execute the stored query using the sp_executesql function. Here is the entire Dynamic SQL Pivot Script:
CREATE TABLE #CustomerProducts ( CustomerID int , ProductName nvarchar(50) , Quantity int) INSERT INTO #CustomerProducts (CustomerID, ProductName, Quantity) VALUES (1, N'Snicker', 1), (1, N'Butterfinger', 1), (1, N'Rolo', 2), (1, N'Milkyway', 1), (1, N'Snicker', 1), (1, N'Butterfinger', 1), (2, N'Rolo', 2), (1, N'Milkyway', 3), (2, N'Twix', 2); DECLARE @Output nvarchar(max) = N'' , @PivotList varchar(max) SELECT @PivotList = COALESCE(@PivotList + ', ', N'') + N'[' + ProductName + N']' FROM ( SELECT DISTINCT ProductName FROM #CustomerProducts ) AS CustProds; SET @Output = 'SELECT [CustomerID] , ' + @PivotList + ' FROM #CustomerProducts P PIVOT ( SUM(Quantity) FOR P.ProductName IN (' + @PivotList + ') ) AS PVT' EXEC sp_executesql @Output; IF OBJECT_ID('tempdb..#CustomerProducts') IS NOT NULL DROP TABLE #CustomerProducts |