-1

I have THREE tables:

  • Table_1 with student names (fields = StudentID, FirstName, LastName)

  • Table_2 with teacher names (fields = TeacherID, FirstName, LastName, Initials*) (*The 'Initials' field holds each teachers initials: 'JD' for 'John Doe', 'FB' for 'Fred Bloggs', etc)

  • Table_3 with the grades that each teacher gave each student (fields = StudentID, TeacherID, Grade)

I would like to generate a query that lists all the students, along with their grades; something like:

FirstName | LastName | JD | FB
==============================
Paul      | Smith    |  7 |  8
Nancy     | Brown    |  5 |  4
Chris     | Nobody   |  6 |  9

The problem is that the columns after FirstName and LastName should be generated "dynamically" from the contents of Table_2 (the teachers giving out the grades). In other words, if a new teacher is added to that table (say, Zack Zanny), then (after he has graded all the students) the same query should yield something like:

FirstName | LastName | JD | FB | ZZ
===================================
Paul      | Smith    |  7 |  8 |  3
Nancy     | Brown    |  5 |  4 |  1
Chris     | Nobody   |  6 |  9 |  2

Did I make this clear?

I've searched this forum and others and I've tried reading up on mysql pivot tables but I must be doing something wrong... Any pointers would be much appreciated. Thanks in advance.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
Paulo_M_
  • 23
  • 5

2 Answers2

0

You can't write an SQL query that dynamically expands its columns based on the data values it finds. The columns in the select-list must be fixed at the time you prepare the query.

So you need to know all the teacher id's before you write the query. One way to do this is to query them:

SELECT TeacherID FROM Table_2;

Then based on the result of that query, your application code needs to build the pivot SQL query with as many columns as you need. In other words, use a loop, appending one column to the select-list for each TeacherID returned by the previous query.

SELECT st.FirstName, st.LastName,
  MAX(CASE TeacherId WHEN 'JD' THEN Grade END) AS `JD`,
  MAX(CASE TeacherId WHEN 'FB' THEN Grade END) AS `FB`,
  MAX(CASE TeacherId WHEN 'ZZ' THEN Grade END) AS `ZZ`
FROM Table_1 AS st
LEFT OUTER JOIN Table_3 AS gr ON st.StudentId = gr.StudentId
GROUP BY st.FirstName, st.LastName;

The MAX() functions are just to satisfy the rule of aggregate queries: every column in the select-list must either by named in the GROUP BY clause, or else be within an aggregation function like MAX(). I assume there will be one grade per teacher per student, so I could just as well have used MIN() or even AVG().

To do a pivot query, you must write the columns explicitly in your query, but you can automate that process a bit by first discovering which teacher ID's exist.

The alternative is to write a query that joins students to grades, which means your app will fetch multiple rows per student — one row per teacher.

SELECT st.FirstName, st.LastName, TeacherId, Grade
FROM Table_1 AS st
LEFT OUTER JOIN Table_3 AS gr ON st.StudentId = gr.StudentId;

Then you loop over the result in your app, and present it in a tabular way however you want.

There's no way around it, to do a pivot, you either write some app code that builds the query before you prepare it, or else you use a fixed query but you write some app code to tabulate results after you fetch the raw data.

Either way, the code isn't rocket science.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • @ bill-karwin : Thanks for the feedback. As you've probably noticed by now, I was looking for the "dynamic sql" approach, to keep everything nicely packed. However, I wanted to thank you for the feedback above. I'm still trying to figure out why the procedure indicated by @ gmb (and the "duplicate" question mentioned here) is not working in my case. – Paulo_M_ Jan 07 '19 at 14:31
  • AFAIK, you can't use phpMyAdmin to do multi-statement solutions like GMB's. Each request is a new session, so it can't use session state established by the previous statement. You could do it in the any application, or in the command-line `mysql` client or MySQL Workbench or a stored procedure which you then call. – Bill Karwin Jan 07 '19 at 15:03
  • @ bill-karin : ahh... :-) That could indeed be an issue. Thanks for point that out. I can write (or paste) various statements into the sql query window and I see that each statement can be executed (and all the results are displayed correctly on the exact same page); but maybe if there are "dependencies" between the statements (as in the case in question), this is not passed along, so to speak. Does anyone out there have experience on this front? Many thanks in anticipation. – Paulo_M_ Jan 08 '19 at 10:13
0

Dynamic pivoting is not natively supported in MySQL, you have to use dynamic sql :

  • query the teachers table to list all ids and initials
  • use the results to build a SQL query
  • run the query

This SO post gives a working example, that I adapted for your use case as follows :

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(g.TeacherID = ''',
      TeacherID,
      ''', g.grade, NULL)) AS ',
      Initials
    )
  ) INTO @sql
FROM table_2;

SET @sql = CONCAT('SELECT 
        s.firstname,
        s.lastname,
        ', @sql, ' 
    FROM
        table_3 g
        INNER JOIN table_1 s ON s.studentid = g.studentid
    GROUP BY 
        s.studentid,
        s.firstname,
        s.lastname';

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
GMB
  • 216,147
  • 25
  • 84
  • 135
  • In my previous searches, I came across the "duplicate" question (and I couldn't get it to play nice with my tables). In fact, that's where the "dynamic" part in my title originally comes from... :-) Unfortunately, I must be doing something ridiculously silly that I'm not aware of because my server keeps complaining and spits out and internal server error (500) instead of playing nice. I'll (again) try going over the example that you so kindly provided and will report back. In case it matters (it shouldn't), I'm running the dynamic sql through phpMyAdmin. Thanks for the feedback so far. – Paulo_M_ Jan 06 '19 at 23:02
  • @Paulo_M_ : ok - as noted, I adapted the example for your use case, it might just work out of the box. – GMB Jan 06 '19 at 23:04
  • @Paulo_M_ If you get http error 500 while using phpmyadmin, then phpmyadmin is incorrectly configured or you encountered a bug within phpmyadmin. Test it with a proper client. – Shadow Jan 08 '19 at 06:30
  • @Paulo_M_ If you get http error 500 while using phpmyadmin, then phpmyadmin is incorrectly configured or you encountered a bug within phpmyadmin. Test it with a proper client. – Shadow Jan 08 '19 at 06:30
  • @ Shadow : I believe that phpMyAdmin is properly configured since I use it for a myriad other things without any problems whatsoever. However, maybe the underlying issue has to do with multi-statement solutions, as pointed out by @ bill-karwin. I'll try looking into this. Thanks for the feedback. – Paulo_M_ Jan 08 '19 at 10:07