Murtaza Mandvi
Murtaza Mandvi

Reputation: 10998

TSQL Pivot data

Data :

id  data
1   1
1   4
2   2
2   1
3   3
3   2
4   4
4   1
5   5
5   2

I need to pivot the above data so that I get the following result :

id  Col1   Col2
1   1        4
2   2        1
3   3        2
4   4        1
5   5        2

I am trying the following query but the columns are always Null :

DECLARE @TEMP TABLE (id int,data varchar(200))

INSERT INTO @TEMP VALUES (1,'1')
INSERT INTO @TEMP VALUES (1,'4')
INSERT INTO @TEMP VALUES (2,'2')
INSERT INTO @TEMP VALUES (2,'1')
INSERT INTO @TEMP VALUES (3,'3')
INSERT INTO @TEMP VALUES (4,'2')
INSERT INTO @TEMP VALUES (4,'4')
INSERT INTO @TEMP VALUES (4,'1')
INSERT INTO @TEMP VALUES (5,'5')
INSERT INTO @TEMP VALUES (5,'2')

SELECT * FROM @TEMP 
    PIVOT
    (
      MAX(Data)
      FOR Data IN ([Col1],[Col2])
    )
    AS p

RESULT:

id  Col1    Col2
1   NULL    NULL
2   NULL    NULL
3   NULL    NULL
4   NULL    NULL
5   NULL    NULL

Upvotes: 0

Views: 128

Answers (2)

Taryn
Taryn

Reputation: 247870

The problem with your query is you don't have any values in your Data column which are named Col1 or Col2.

When you are writing a PIVOT query the critical part of the query is:

pivot
(
  max(data)  -- < these are the values that you need displayed in the new columns
  for col in (Col1, Col2)  -- < this is the new column names from existing values
) p

Your query is using Col1, Col2 but those aren't defined anywhere so you are returning null.

You currently don't have anything that could be used as the new column names. In order to get the result, you will want to use a windowing function similar to row_number to generate a unique sequence over each id. The basic syntax will be:

select id, Col1, Col2
from
(
  select id, data,
    col =
      'Col'+
        cast(row_number() over(partition by id
                                order by data) as varchar(2))
  from yourtable
) d
pivot
(
  max(data)
  for col in (Col1, Col2)
) p;

See SQL Fiddle with Demo. You'll notice that in the subquery I use the windowing function to create the new column names Col1, Col2, etc. This is then used in the pivot.

Now for your situation if you are going to have an unknown number of possible columns, then you will need to use dynamic SQL. This creates a SQL string that will then be executed:

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

select @cols = STUFF((SELECT ',' + QUOTENAME(col) 
                    from
                    (
                      select col =
                        'Col'+
                          cast(row_number() over(partition by id
                                                  order by data) as varchar(2))
                      from yourtable
                    ) d
                    group by Col
                    order by col
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT id, ' + @cols + ' 
            from 
            (
              select id, data,
                col =
                  ''Col''+
                    cast(row_number() over(partition by id
                                            order by data) as varchar(2))
              from yourtable
            ) x
            pivot 
            (
                max(Data)
                for col in (' + @cols + ')
            ) p '

exec sp_executesql @query;

See SQL Fiddle with Demo.

Finally, this could also be done without using PIVOT by using an aggregate function with a CASE expression:

select id, 
  Col1 = max(case when seq = 1 then data end), 
  Col2 = max(case when seq = 2 then data end)
from
(
  select id, data,
    seq = row_number() over(partition by id
                            order by data)
  from yourtable
) d
group by id;

See SQL Fiddle with Demo. All versions give the same result:

| ID | COL1 | COL2 |
|----|------|------|
|  1 |    1 |    4 |
|  2 |    1 |    2 |
|  3 |    2 |    3 |
|  4 |    1 |    4 |
|  5 |    2 |    5 |

Upvotes: 9

shree.pat18
shree.pat18

Reputation: 21757

You can do this without a PIVOT like so:

;WITH CTE AS   
(
 SELECT 
 *,
 ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) RN 
 FROM @TEMP)

SELECT DISTINCT t.id, c1.data, c2.data
FROM @temp t
INNER JOIN CTE c1 ON c1.id = t.id AND c1.rn = 1
INNER JOIN CTE c2 ON c2.id = t.id AND c2.rn = 2

Note that this will work in this specific case. However, you should try to understand @bluefeet's answer to get a thorough understanding of how PIVOT works and how you can use it if you had multiple columns in another scenario.

Upvotes: 3

Related Questions