pivotsql-server-2014dynamic-sqlunpivot

Pivoting/Unpivoting multiple columns using dynamic SQL


I have the below requirement.

CREATE TABLE #TempData 
(
    Columnname1 NVARCHAR(50),
    Value1 NVARCHAR(50),
    Columnname2 NVARCHAR(50),
    Value2 NVARCHAR(50),
    Columnname3 NVARCHAR(50),
    Value3 NVARCHAR(50)
);

Insert sample data into the temporary table

INSERT INTO #TempData (Columnname1, Value1, Columnname2, Value2, Columnname3, Value3)
VALUES
('CustomerID', '1', 'CustomerName', 'A', 'CustomerCity', 'Hyd'),
('CustomerID', '2', 'CustomerName', 'B', 'CustomerCity', 'Mum'),
('CustomerID', '3', 'CustomerName', 'C', 'CustomerCity', 'Pune'),
('CustomerID', '4', 'CustomerName', 'D', 'CustomerCity', 'Nagpur'),
('CustomerID', '5', 'CustomerName', 'E', 'CustomerCity', 'Delhi'),
('CustomerID', '6', 'CustomerName', 'F', 'CustomerCity', 'Noida'),
('CustomerID', '1', 'CustomerDOB', 'July', NULL, NULL),
('CustomerID', '2', 'CustomerDOB', 'August', NULL, NULL),
('CustomerID', '3', 'CustomerDOB', 'September', NULL, NULL),
('CustomerID', '4', 'CustomerDOB', 'October', NULL, NULL),
('CustomerID', '5', 'CustomerDOB', 'November', NULL, NULL),
('CustomerID', '6', 'CustomerDOB', 'December', NULL, NULL);`

Above is just a sample. Apart from the columnname1, rest of the columns(Column name2,3 etc) can have multiple columns as shown in the columnname2.

So I need to make sure that the result can look as below.

Result set:

CustomerID  CustomerName    CustomerCity    CustomerDOB
--------------------------------------------------------
1           A               Hyd             July
2           B               Mum             August
3           C               Pune            September
4           D               Nagpur          October
5           E               Delhi           November
6           F               Noida           December

All this need to be dynamic as the column names are not static. It may vary with each and every requirement. Can you please provide me with the dynamic SQL in SQL Server 2014 only to make this work?

I tried the below. But I'm unable to make it work at once for an entire #temp table:

DECLARE @DynamicPivotQuery1 AS NVARCHAR(MAX)
DECLARE @DynamicPivotQuery2 AS NVARCHAR(MAX)
DECLARE @DynamicPivotQuery3 AS NVARCHAR(MAX)

DECLARE @ColumnName1 AS NVARCHAR(MAX)
DECLARE @ColumnName2 AS NVARCHAR(MAX)
DECLARE @ColumnName3 AS NVARCHAR(MAX)


-- Get distinct values of the PIVOT Column 
SELECT @ColumnName1 = ISNULL(@ColumnName1 + ',','') + QUOTENAME(Columnname1)
FROM (SELECT DISTINCT Columnname1 FROM #TempData) AS Columnnames

SELECT @ColumnName2 = ISNULL(@ColumnName2 + ',','') + QUOTENAME(Columnname2)
FROM (SELECT DISTINCT Columnname2 FROM #TempData) AS Columnnames

SELECT @ColumnName3 = ISNULL(@ColumnName3 + ',','') + QUOTENAME(Columnname3)
FROM (SELECT DISTINCT Columnname3 FROM #TempData) AS Columnnames

-- Create the dynamic PIVOT query
SET @DynamicPivotQuery1 = 
  N'SELECT DISTINCT ' + @ColumnName1 + ' 
    FROM 
    (
        SELECT Columnname1, Value1, Columnname2, Value2,Columnname3, Value3 
        FROM #TempData
    ) AS SourceTable
    PIVOT(MAX(Value1) 
          FOR Columnname1 IN (' + @ColumnName1 + ')) AS PivotedTable'

PRINT @DynamicPivotQuery1
EXEC sp_executesql @DynamicPivotQuery1

SET @DynamicPivotQuery2 = 
  N'SELECT ' + @ColumnName2 + '
    FROM 
    (
        SELECT Columnname1, Value1, Columnname2, Value2,Columnname3, Value3 
        FROM #TempData
    ) AS SourceTable
    PIVOT(MAX(Value2) 
          FOR Columnname2 IN (' + @ColumnName2 + ')) AS PivotedTable'

-- Execute the dynamic query
PRINT @DynamicPivotQuery2
EXEC sp_executesql @DynamicPivotQuery2

SET @DynamicPivotQuery3 = 
  N'SELECT ' + @ColumnName3 + '
    FROM 
    (
        SELECT Columnname1, Value1, Columnname2, Value2,Columnname3, Value3 
        FROM #TempData
        WHERE Columnname3 IS NOT NULL
    ) AS SourceTable
    PIVOT(MAX(Value3) 
          FOR Columnname3 IN (' + @ColumnName3 + ')) AS PivotedTable'
--MAX(CASE WHEN ColumnName1 = 'CustomerID' THEN Value1 END) as CustomerID
-- Execute the dynamic query
PRINT @DynamicPivotQuery3
EXEC sp_executesql @DynamicPivotQuery3

Solution

  • When you have to PIVOT more than one column, I find it much easier to conceptualize using conditional aggregation. If we think backward from a conditional aggregate query that will generate the results you need, given that ColumnName1 is fixed and that Value1 will always hold the CustomerID:

    SELECT CustomerID = Value1, 
      CustomerName = MAX(CASE WHEN ColumnName2 = 'CustomerName' THEN Value2 END),
      CustomerCity = MAX(CASE WHEN ColumnName3 = 'CustomerCity' THEN Value3 END),
      CustomerDOB  = MAX(CASE WHEN ColumnName2 = 'CustomerDOB'  THEN Value2 END)
    FROM #TempData
    GROUP BY Value1;
    

    Since you are on an ancient and unsupported version without the benefit of STRING_AGG(), we can get there from dynamic SQL using FOR XML PATH:

    DECLARE @cases nvarchar(max),
            @sql   nvarchar(max);
    
    WITH x AS 
    (
      -- one way to UNPIVOT first is to use UNION ALL
      SELECT s = N'ColumnName2', c = ColumnName2, v = N'Value2'
        FROM #TempData GROUP BY ColumnName2
      UNION ALL 
      SELECT s = N'ColumnName3', c = ColumnName3, v = N'Value3'
        FROM #TempData GROUP BY ColumnName3
    )
    SELECT @cases = (SELECT CONCAT(',', char(13), char(10), 
        QUOTENAME(c), N' = MAX(CASE WHEN ', s, 
        N' = ', QUOTENAME(c, char(39)), N' THEN ', v, N' END)') 
        FROM x WHERE c IS NOT NULL
        FOR XML PATH(''), TYPE).value(N'.[1]', N'nvarchar(max)');
    
    SELECT @sql = CONCAT(N'SELECT CustomerID = Value1',
      @cases, N'
      FROM #TempData GROUP BY Value1;');
    
    PRINT @sql;
    EXEC sys.sp_executesql @sql;