skip to Main Content

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:

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.

2

Answers


  1. 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
    Login or Signup to reply.
  2. While MySQL has no PIVOT function, you could have make use of
    CASE WHEN – THEN in this way: (code not tested)

    SELECT
    LastName
    ,FirstName
    ,CASE WHEN ca.Class_ID  = 1 THEN COUNT(ca.Class_ID) ELSE null END AS Geography
    ,CASE WHEN ca.Class_ID  = 2 THEN COUNT(ca.Class_ID) ELSE null END AS Science
    ,CASE WHEN ca.Class_ID  = 3 THEN COUNT(ca.Class_ID) ELSE null END AS English
    ,CASE WHEN ca.Class_ID  = 4 THEN COUNT(ca.Class_ID) ELSE null END AS History
    
    FROM
    Class_attendees ca
    INNER JOIN students s ON ca.Student_ID=s.ID
    INNER JOIN Class_Groups cg on s.ClassID=cg.ID
    
    WHERE cg.Class_Name='Blue'
    
    GROUP BY
    ca.Student_ID
    

    Class_id column in table Students has same name of Class ID in Class_attendees table but two different meanings, you should choose better naming.

    Info in Classes tables is replicated in CASE WHEN instructions

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search