Search code examples
sqlsql-serverpivotpivot-table

SQL Pivot to create 2 columns per row


I have a table that looks like this:

Date Name Actual Target
2023-03-14 Bob 15 15
2023-03-14 Jim 9 5
2023-03-14 Steve 10 9
2023-03-15 Bob 11 11
2023-03-15 Jim 16 16
2023-03-15 Steve 5 12

The Name column isn't always the same depending on who is working so the pivot has to be dynamic. I want the table to look like this:

Date Bob_Actual Bob_Target Jim_Actual Jim_Target Steve_Actual Steve_Target
2023-03-14 15 15 9 5 10 9
2023-03-15 11 11 16 16 5 12

I was able to get this desired result if I just dynamically pivot on target, but I can't figure out how to pivot both the target and the actual

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(Name) 
                    from yt
                    group by Name
                    order by Name
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT Date,' + @cols + ' from 
             (
                select Date, Name, Target
                from yt
            ) x
            pivot 
            (
                sum(Target)
                for Name in (' + @cols + ')
            ) p '

execute(@query);

I used the above code to get it to work for just target, but am not sure how to add actual


Solution

  • Don't use pivots, they're not flexible enough, try conditional aggregation instead:

    SELECT  *
    INTO #yt
    FROM    (
        VALUES  (N'2023-03-14', N'Bob', 15, 15)
        ,   (N'2023-03-14', N'Jim', 9, 5)
        ,   (N'2023-03-14', N'Steve', 10, 9)
        ,   (N'2023-03-15', N'Bob', 11, 11)
        ,   (N'2023-03-15', N'Jim', 16, 16)
        ,   (N'2023-03-15', N'Steve', 5, 12)
    ) t (Date,Name,Actual,Target)
    
    DECLARE @cols AS NVARCHAR(MAX)
    ,   @query  AS NVARCHAR(MAX)
    
    SELECT  @cols = (   
        SELECT  '
        ,   SUM(case when name = ' + QUOTENAME(Name, '''') + ' then Actual END) AS ' + QUOTENAME(Name + '_Actual') + N'
        ,   SUM(case when name = ' + QUOTENAME(Name, '''') + ' then Target END) AS ' + QUOTENAME(Name + '_Targer')
        FROM    #yt
        GROUP BY Name
        ORDER BY Name
        FOR XML PATH(''), TYPE
                ).value('text()[1]', 'NVARCHAR(MAX)') 
    
    SET @query = '
        SELECT  Date' + @cols + '
        FROM    #yt
        GROUP BY Date
                '
    EXEC(@query);
    

    This way, you have much more flexibility in handling both column names and values. The SUM(CASE WHEN ...) is the conditional aggregation part that fetches the needed values and places them in correct columns.