Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

A SELECT using another table's rows to create and name columns – Select every student in "Blue" class with a separate column for each subject taken

I’m struggling to know where to start on this one. I want to write a SELECT query which will return all the students in a particular class and the subjects they each take. But the subjects are stored in rows of another table and I need to somehow make them become a new column for every subject.

I have four tables:

Class Groups:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

ID Class Name Teacher
1 Blue Mr Smith
2 Red Miss Brown

Students:

ID ClassID Last Name First Name
1 1 Cooper Sarah
2 2 Jones John
3 1 Moody Claire
4 1 Baggins Frodo

Classes:

ID Class
1 Geography
2 Science
3 English
4 History
5 Spanish

Class_attendees

Class ID Student_ID
1 1
1 2
1 4
2 1
2 5
3 2
3 3
3 4
4 1
4 2
4 3

I want to write a SELECT query that shows me the timetable for Blue Class that would look like this.

Last Name First Name Geography Science English History
Cooper Sarah 1 1 1
Moody Claire 1 1 1
Baggins Frodo 1 1 1

Since no-one in Blue Class is taking Spanish I don’t need a column for it.

>Solution :

I want to write a SELECT query which will return all the students in a particular class and the subjects they each take. But the subjects are stored in rows of another table and I need to somehow make them become a new column for every subject

I want to write a SELECT query that shows me the timetable for Blue Class…

The first part is a straightforward JOIN – but the second part ("…make them become a new column" and "shows me the timetable for") is not possible in SQL nor is it supported by the relational-model that SQL is based on, because the "timetable" view you describe is not an example of relational-data (btw, the term "relational" does not refer to "relationships" or foreign-keys; please see Codd’s paper I linked to).

One of the fundamental rules here (paraphrased) is that columns are static (i.e. your SQL query defines the columns directly: e.g. a, b, c in SELECT a, b c FROM d), so when dealing with data that may-or-may-not-exist (or instead, not-only exists, but has a very high cardinality) then that’s all in the rows/tuples.

So your internal data representation of these undefined columns based on your not-yet-known-in-advance query results cannot have these dynamic columns, but that’s okay because the job of rendering a table/data-grid with dynamic columns is best done by your presentation layer (presumably PHP to generate a HTML <table> or some Report/templating engine you’re using?) – but not using only SQL.

(Yes, you can hack it to work by using Dynamic SQL but let’s just not go there. There is also PIVOT/UNPIVOT, but that still requires you to know what the columns are in-advance when you write the query).

Anyway, the first part is easily done:

SELECT
    s.LastName,
    s.FirstName,
    c.ClassName
FROM
    Class_attendees AS ca
    INNER JOIN Students AS s ON ca.Student_ID = s.ID
    INNER JOIN Classes  AS c ON ca.Class_Id   = c.ID
WHERE
    s.ClassID = 1
ORDER BY
    s.LastName,
    s.FirstName,
    s.ID /* Using this as a tie-breaker if two students have the same name */,
    c.ClassName

…which will give you this:

LastName FirstName ClassName
Cooper Sarah Geography
Cooper Sarah Science
Cooper Sarah History
Moody Claire Science
Moody Claire English
Moody Claire History
Baggins Frodo Geography
Baggins Frodo Science
Baggins Frodo English

…and as said, the task of flattening this into that "timetable" view will be done by your presentation-layer and cannot be done using SQL.


…but if you don’t care about de jure columns, you can still flatten the results using GROUP_CONCAT:

WITH studentClasses AS (

    SELECT
        s.ID AS StudentId,
        s.LastName,
        s.FirstName,
        c.ClassName
    FROM
        Class_attendees AS ca
        INNER JOIN Students AS s ON ca.Student_ID = s.ID
        INNER JOIN Classes  AS c ON ca.Class_Id   = c.ID
    WHERE
        s.ClassID = 1
)
SELECT
    g.LastName,
    g.FirstName,
    GROUP_CONCAT( g.ClassName ORDER BY g.ClassName SEPARATOR ', ' ) AS Classes
FROM
    studentClasses AS g
GROUP BY
    g.StudentId /* One should always prefer to GROUP BY actual keys rather than _plain ol' data_ columns; in this case, it means we avoid the pitfull of grouping distinct students that have the same name. */
    /* I assume you're using a modern version of MySQL that detects functional-dependencies in GROUP BY clauses (see https://dev.mysql.com/doc/refman/8.4/en/group-by-functional-dependence.html ) */
ORDER BY
    g.LastName,
    g.FirstName,
    g.StudentId

Gives you…

LastName FirstName Classes
Cooper Sarah Geography, Science, History
Moody Claire Science, English, History
Baggins Frodo Geography, Science, English
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading