Search code examples
sqlsql-server-2008pivotunpivot

Add more columns for each value of another column?


I have this kind of table :

Name   Date      Value
-----------------------
Test 1/1/2001   10
Test 2/1/2001   17
Test 3/1/2001   52
Foo  5/4/2011   15
Foo  6/4/2011   321
My   15/5/2005  36
My   25/7/2005  75

And I would like to show the results like this :

Name   Date      Value  Name   Date      Value  Name   Date      Value
---------------------------------------------------------------------
Test 1/1/2001    10      Foo  5/4/2011   15      My   15/5/2005  36
Test 2/1/2001    17      Foo  6/4/2011   321     My   25/7/2005  75
Test 3/1/2001    52

I need to show as many columns as what is present in my Name column

How could I do this in Sql ?


Solution

  • In order to get the result that you want, you are going to have to unpivot the columns in your table and apply the pivot function.

    The unpivot can be done using either the UNPIVOT function or you can use CROSS APPLY with VALUES.

    UNPIVOT:

    select rn, 
      col +'_'+cast(dr as varchar(10)) col, 
      new_values
    from
    (
      select name, 
        convert(varchar(10), date, 101) date, 
        cast(value as varchar(10)) value,
        dense_rank() over(order by name) dr,
        row_number() over(partition by name order by date) rn
      from yourtable
    ) d
    unpivot
    (
      new_values
      for col in (name, date, value)
    ) un;
    

    CROSS APPLY:

    select rn, 
      col +'_'+cast(dr as varchar(10)) col, 
      c.value
    from
    (
      select name, 
        convert(varchar(10), date, 101) date, 
        cast(value as varchar(10)) value,
        dense_rank() over(order by name) dr,
        row_number() over(partition by name order by date) rn
      from yourtable
    ) d
    cross apply
    (
      values
        ('Name', name), ('Date', date), ('Value', Value)
    ) c (col, value);
    

    See SQL Fiddle with Demo of both versions. This gives the result:

    | RN |     COL | NEW_VALUES |
    -----------------------------
    |  1 |  name_1 |        Foo |
    |  1 |  date_1 | 04/05/2011 |
    |  1 | value_1 |         15 |
    |  2 |  name_1 |        Foo |
    |  2 |  date_1 | 04/06/2011 |
    |  2 | value_1 |        321 |
    |  1 |  name_2 |         My |
    |  1 |  date_2 | 05/15/2005 |
    |  1 | value_2 |         36 |
    

    These queries take your existing columns values and converts them to rows. Once they are in rows, you create the new column names by using the windowing function dense_rank.

    Once the data has been converted to rows, you then use the new column names (created with the dense_rank value) and apply the PIVOT function.

    PIVOT with UNPIVOT:

    select name_1, date_1, value_1,
      name_2, date_2, value_2,
      name_3, date_3, value_3
    from
    (
      select rn, 
        col +'_'+cast(dr as varchar(10)) col, 
        new_values
      from
      (
        select name, 
          convert(varchar(10), date, 101) date, 
          cast(value as varchar(10)) value,
          dense_rank() over(order by name) dr,
          row_number() over(partition by name order by date) rn
        from yourtable
      ) d
      unpivot
      (
        new_values
        for col in (name, date, value)
      ) un
    ) src
    pivot
    (
      max(new_values)
      for col in (name_1, date_1, value_1,
                  name_2, date_2, value_2,
                  name_3, date_3, value_3)
    ) piv;
    

    See SQL Fiddle with Demo

    PIVOT with CROSS APPLY:

    select name_1, date_1, value_1,
      name_2, date_2, value_2,
      name_3, date_3, value_3
    from
    (
      select rn, 
        col +'_'+cast(dr as varchar(10)) col, 
        c.value
      from
      (
        select name, 
          convert(varchar(10), date, 101) date, 
          cast(value as varchar(10)) value,
          dense_rank() over(order by name) dr,
          row_number() over(partition by name order by date) rn
        from yourtable
      ) d
      cross apply
      (
        values
          ('Name', name), ('Date', date), ('Value', Value)
      ) c (col, value)
    ) src
    pivot
    (
      max(value)
      for col in (name_1, date_1, value_1,
                  name_2, date_2, value_2,
                  name_3, date_3, value_3)
    ) piv;
    

    See SQL Fiddle with Demo.

    Dyanmic PIVOT:

    The above versions will work great if you have a limited or known number of columns, if not, then you will need to use dynamic SQL:

    DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @cols = STUFF((SELECT ',' + QUOTENAME(col +'_'+cast(dr as varchar(10)))
                        from 
                        (
                          select dense_rank() over(order by name) dr
                          from yourtable
                        ) t
                        cross apply
                        (
                          values(1, 'Name'), (2, 'Date'), (3, 'Value')
                        ) c (sort, col)
                        group by col, dr, sort
                        order by dr, sort
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    
    set @query = 'SELECT ' + @cols + ' 
                  from 
                 (
                    select rn, 
                      col +''_''+cast(dr as varchar(10)) col, 
                      c.value
                    from
                    (
                      select name, 
                        convert(varchar(10), date, 101) date, 
                        cast(value as varchar(10)) value,
                        dense_rank() over(order by name) dr,
                        row_number() over(partition by name order by date) rn
                      from yourtable
                    ) d
                    cross apply
                    (
                      values
                        (''Name'', name), (''Date'', date), (''Value'', Value)
                    ) c (col, value)
                ) x
                pivot 
                (
                    max(value)
                    for col in (' + @cols + ')
                ) p'
    
    execute(@query)
    

    See SQL Fiddle with Demo.

    The result for each of the queries is:

    | NAME_1 |     DATE_1 | VALUE_1 | NAME_2 |     DATE_2 | VALUE_2 | NAME_3 |     DATE_3 | VALUE_3 |
    -------------------------------------------------------------------------------------------------
    |    Foo | 04/05/2011 |      15 |     My | 05/15/2005 |      36 |   Test | 01/01/2001 |      10 |
    |    Foo | 04/06/2011 |     321 |     My | 07/25/2005 |      75 |   Test | 01/02/2001 |      17 |
    | (null) |     (null) |  (null) | (null) |     (null) |  (null) |   Test | 01/03/2001 |      52 |