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.