Reputation: 571
I've got a query that returns data like so:
student | course | grade |
---|---|---|
a-student | ENG-W05 | 100 |
a-student | MAT-W05 | 85 |
a-student | ENG-W06 | 100 |
b-student | MAT-W05 | 90 |
b-student | SCI-W05 | 75 |
The data is grouped by student and course. Ideally, I'd like to have the above data transformed into the below:
student | ENG-W05 | MAT-W05 | ENG-W06 | SCI-W05 |
---|---|---|---|---|
a-student | 100 | 85 | 100 | NULL |
b-student | NULL | 90 | NULL | 75 |
So, after the transformation, each student only has one record, with all of their grades (and any missing courses graded as null).
Does anyone have any ideas? Obviously, this is fairly simple to do if I take the data out and transform it in a language (like Python), but I'd love to get the data in the desired format with an SQL query.
Also, would it be possible to have the columns order alphabetically (ascending)? So, the final output would be:
student | ENG-W05 | ENG-W06 | MAT-W05 | SCI-W05 |
---|---|---|---|---|
a-student | 100 | 100 | 85 | NULL |
b-student | NULL | NULL | 90 | 75 |
EDIT: To clarify, the values in course
aren't known. The ones I provided are just examples. So ideally, if more course values found there way into that first query result (the first table), they would still be mapped to columns in the final result (without needing to change the query). In reality, I actually have >1k distinct values for the course
column, and so I can't manually write out each one.
Upvotes: 1
Views: 43
Reputation: 23686
You can use conditional aggregation for that:
SELECT
student,
SUM(grade) FILTER (WHERE course = 'ENG-W05') as eng_w05,
SUM(grade) FILTER (WHERE course = 'MAT-W05') as mat_w05,
SUM(grade) FILTER (WHERE course = 'ENG-W06') as eng_w06,
SUM(grade) FILTER (WHERE course = 'SCI-W05') as sci_w05
FROM mytable
GROUP BY student
The FILTER
clause allows to aggregate only some specific records. So this one aggregates all records for a specific course.
Finding the correct aggregate function could be difficult. Here SUM()
does the job, as there's only one value per group. MAX()
or MIN()
would do it as well. It depends on your real requirement. If there's really only one value per group, it doesn't matter, you just need to do any aggregation.
Instead of FILTER
clause, which is Postgres specific, you could use the more SQL standard fitting CASE
clause:
SELECT
student,
SUM(
CASE
WHEN course = 'ENG-W05' THEN grade
END
) AS eng_w05,
...
Upvotes: 1
Reputation: 35910
You can use the conditional aggregation as follows:
select student,
max(case when course = 'ENG-W05' then grade end) as "ENG-W05",
max(case when course = 'MAT-W05' then grade end) as "MAT-W05",
max(case when course = 'ENG-W06' then grade end) as "ENG-W06",
max(case when course = 'SCI-W05' then grade end) as "SCI-W05"
from (your_query) t
group by student
Upvotes: 1