Pergunta

I am trying to do a Pivot on my table to output the rows as columns. I have seen a few examples over the internet but I get lost everytime the explanation gets to the agregation (which I think is not relevant to what I want to achieve?). I have the following Class Table:

StudentID       ClassCode
10001           ENG240
10001           MTH100
10001           BIO101
10001           HUM300
10002           PHY200
10002           PHY200-L
10002           MTH100
10002           HUM200
10002           CHR100
10002           COM140
10003           HUM100
10003           ENG200
10003           PHY101

What I want to get is the following output:

StudentID       ClassCode 1     ClassCode 2     ClassCode 3     ClassCode 4     ClassCode 5
10001           ENG240          MTH100          BIO101          HUM300
10002           PHY200          PHY200-L        MTH100          HUM200          CHR100
10002           COM140
10003           HUM100          ENG200          PHY101    

The pivot fields should only be five columns at max. If there are students having more than five classes, then a new record should be added to the result set.

Can anyone please point me to a good way to achieve this? Thakns so much!

*EDIT: *

As of this moment, I am able to pivot the table using my query below:

CREATE 
  TABLE  #TestClass 
         (StudentID INT, row INT, ClassCode VARCHAR(32))


  ;WITH  TCSPivot(StudentID, row, ClassCode)
     AS
        (SELECT  StudentID,
                 row_number() OVER(PARTITION BY StudentID ORDER BY StudentID, ClassCode),
                 ClassCode
           FROM  student_class
         )

 INSERT 
   INTO  #TestClass
 SELECT  p.StudentID, 
         p.row, 
         p.ClassCode
   FROM  MyPivot p
   JOIN  class c
     ON  c.ClassCode   = p.ClassCode


    SELECT  @sql = @sql + ', MAX(CASE WHEN row = ' + CAST(tc.row AS CHAR(5)) + ' THEN ClassCode ELSE '''' END) AS [ClassCode ' + CAST(tc.row AS CHAR(5)) + ']'
       FROM  #TestClass tc 
      GROUP 
         BY  tc.row
      ORDER  
         BY  tc.row

        SET  @sql = @sql + N' 
                            FROM  #TestClass
                           GROUP 
                              BY  StudentID
                           ORDER 
                              BY  StudentID'

       EXEC  sp_executesql @sql

What I need to do now is how to restrict that only 5 records should be pivoted vertically. If there are StudentIDs with classes more than 5, then a second record should be added.

Thanks everyone!!

Foi útil?

Solução

You can easily get the result by implementing 2 windowing functions, ntile() and row_number().

NTILE() will be used to partition your data into "buckets", so when you use NTILE(5) you are going to create 5 buckets for your ClassCodes for each StudentId.

select StudentId, ClassCode,
  newCol =
   'ClassCode' +
      cast(ntile(5) over(partition by StudentId 
                         order by ClassCode) as varchar(1))
from TestClass;

See SQL Fiddle with Demo. This will get your data into the format:

| STUDENTID | CLASSCODE |     NEWCOL |
|-----------|-----------|------------|
|     10002 |    CHR100 | ClassCode1 |
|     10002 |    COM140 | ClassCode1 |
|     10002 |    HUM200 | ClassCode2 |
|     10002 |    MTH100 | ClassCode3 |
|     10002 |    PHY200 | ClassCode4 |
|     10002 |  PHY200-L | ClassCode5 |

As you can see the data is now in 5 buckets, these buckets are your new column names ClassCode1, ClassCode2, etc. You will also notice that there are two rows with the ClassCode1, if you apply the PIVOT function now you will only return one row. In order to return multiple rows you will need to apply row_number() to the data.

The row_number() will create a unique sequence for each row of data:

;with cte as
(
  select StudentId, ClassCode,
    newCol =
      'ClassCode' +
        cast(ntile(5) over(partition by StudentId 
                           order by ClassCode) as varchar(1))
  from TestClass
),
mr as
(
  select StudentId, ClassCode,
    newCol,
    row_number() over(partition by StudentId, newCol order by newCol) seq
  from cte
)
select *
from mr;

See SQL Fiddle with Demo. This gets a result of:

| STUDENTID | CLASSCODE |     NEWCOL | SEQ |
|-----------|-----------|------------|-----|
|     10002 |    CHR100 | ClassCode1 |   1 |
|     10002 |    COM140 | ClassCode1 |   2 |
|     10002 |    HUM200 | ClassCode2 |   1 |
|     10002 |    MTH100 | ClassCode3 |   1 |
|     10002 |    PHY200 | ClassCode4 |   1 |
|     10002 |  PHY200-L | ClassCode5 |   1 |

The NewCol values that were identical ClassCode1 now have a different sequence number. This will be needed when grouping by your data during the pivot process.

Finally, you can apply the PIVOT function to get the final result:

;with cte as
(
  select StudentId, ClassCode,
    newCol =
      'ClassCode' +
        cast(ntile(5) over(partition by StudentId 
                           order by ClassCode) as varchar(1))
  from TestClass
),
mr as
(
  select StudentId, ClassCode,
    newCol,
    row_number() over(partition by StudentId, newCol order by newCol) seq
  from cte
)
select studentid,
  ClassCode1, ClassCode2, ClassCode3, 
  ClassCode4, ClassCode5
from  mr
pivot
(
  max(ClassCode)
  for NewCol in (ClassCode1, ClassCode2, ClassCode3, 
                 ClassCode4, ClassCode5)
) piv
order by StudentId;

See SQL Fiddle with Demo.

If you wanted to use an aggregate function with a CASE expression like you had in your question then you would still use NTILE() and row_number() but the final code would be:

;with cte as
(
  select StudentId, ClassCode,
    newCol =
      'ClassCode' +
        cast(ntile(5) over(partition by StudentId 
                           order by ClassCode) as varchar(1))
  from TestClass
),
mr as
(
  select StudentId, ClassCode,
    newCol,
    row_number() over(partition by StudentId, newCol order by newCol) seq
  from cte
)
select studentid,
  max(case when newcol = 'ClassCode1' then ClassCode end) ClassCode1,
  max(case when newcol = 'ClassCode2' then ClassCode end) ClassCode2,
  max(case when newcol = 'ClassCode3' then ClassCode end) ClassCode3,
  max(case when newcol = 'ClassCode4' then ClassCode end) ClassCode4,
  max(case when newcol = 'ClassCode5' then ClassCode end) ClassCode5
from  mr
group by StudentId, seq
order by StudentId;

See SQL Fiddle with Demo. Both versions will give a final result of:

| STUDENTID | CLASSCODE1 | CLASSCODE2 | CLASSCODE3 | CLASSCODE4 | CLASSCODE5 |
|-----------|------------|------------|------------|------------|------------|
|     10001 |     BIO101 |     ENG240 |     HUM300 |     MTH100 |     (null) |
|     10002 |     CHR100 |     HUM200 |     MTH100 |     PHY200 |   PHY200-L |
|     10002 |     COM140 |     (null) |     (null) |     (null) |     (null) |
|     10003 |     ENG200 |     HUM100 |     PHY101 |     (null) |     (null) |

Since you are going to only have 5 columns, there shouldn't be a need to use dynamic SQL to get the result.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top