Search code examples
postgresqlpivot-tablepostgresql-9.1crosstabtable-functions

How to get dynamic number of columns in Postgresql crosstab


I'm new to the postgresql crosstab function and have tried out a few solutions here on SO but still stuck. So basically I have a query that result in an output like the one below:

|student_name|subject_name|marks|
|------------|------------|-----|
|John Doe    |ENGLISH     |65   |
|John Doe    |MATHEMATICS |72   |
|Mary Jane   |ENGLISH     |74   |
|Mary Jane   |MATHEMATICS |70   |
|------------|------------|-----|

And the output I'm aiming for with crosstab is:

|student_name| ENGLISH | MATHEMATICS |
|------------|---------|-------------|
|John Doe    | 65      | 72          |
|Mary Jane   | 74      | 70          |
|------------|---------|-------------|

My query that returns the first table (without crosstab) is:

SELECT student_name, subject_name, sum(marks) as marks FROM (
    SELECT student_id, student_name, class_name, exam_type, subject_name, total_mark as marks, total_grade_weight as out_of, percentage, grade, sort_order
    FROM(
        SELECT  student_id, student_name, class_name, exam_type, subject_name, total_mark, total_grade_weight, ceil(total_mark::float/total_grade_weight::float*100) as percentage,
            (select grade from app.grading where (total_mark::float/total_grade_weight::float)*100 between min_mark and max_mark) as grade, sort_order
        FROM (
            SELECT --big query with lots of JOINS
        ) q ORDER BY sort_order
    )v GROUP BY v.student_id, v.student_name, v.class_name, v.exam_type, v.subject_name, v.total_mark, v.total_grade_weight, v.percentage, v.grade, v.sort_order
    ORDER BY student_name ASC, sort_order ASC
    )a
    GROUP BY student_name, subject_name
ORDER BY student_name

And for the crosstab, this is where I get stuck with the columns.

SELECT * FROM 
crosstab(' //the query above here ',
          $$VALUES ('MATHEMATICS'::text), ('marks')$$
        ) AS ct 
(student_name text, subject_name character varying, marks numeric);

If I run it as shown above, this is what I end up with:

|student_name|subject_name|marks|
|------------|------------|-----|
|John Doe    | 65         |     |
|Mary Jane   | 74         |     |
|____________|____________|_____|

As in it says subject_name not ENGLISH or MATHEMATICS. Obviously now I see I don't need the marks column but how can I get it to pull all the subject names as the column names? They could be two, they could be 12.


Solution

  • Solved it, but I would have preferred a much more dynamic solution. I replaced this;

    $$VALUES ('MATHEMATICS'::text), ('marks')$$ with this;

    'SELECT subject_name FROM app.subjects WHERE ... ORDER BY ...' The downside to my solution is that the last part changes to

    (student_name text, english bigint, mathematics bigint, physics bigint, biology bigint, chemistry bigint, history bigint, ...);

    That is, I have to list all the subjects manually and exactly in the order they are listed in from the above select. I don't find this very convenient but it works.