6

Given the following tables:

Student

+----+-------+
| id | Name  |
+----+-------+
| 1  | Chris |
| 2  | Joe   |
| 3  | Jack  |
+----+-------+

Enrollment

+---------------+------------+-----------+----------+
| enrollment_id | student_id | course_id | complete |
+---------------+------------+-----------+----------+
| 1             | 1          | 55        | true     |
| 2             | 1          | 66        | true     |
| 3             | 1          | 77        | true     |
| 4             | 2          | 55        | true     |
| 5             | 2          | 66        | false    |
| 6             | 3          | 55        | false    |
| 7             | 3          | 66        | true     |
+---------------+------------+-----------+----------+

I would like the following

+----+-------+-----------+-----------+-----------+
| id | Name  | Course 55 | Course 66 | Course 77 |
+----+-------+-----------+-----------+-----------+
| 1  | Chris | true      | true      | true      |
| 2  | Joe   | true      | false     | NULL      |
| 3  | Jack  | false     | true      | NULL      |
+----+-------+-----------+-----------+-----------+

Note 1: I know mysql can't have dynamic columns (correct me if I'm wrong!) so I am happy with the query starting as:

SELECT id, name, course_55, course_66, course_77 etc...

I happy with this because there is a fixed number of courses (4 to be exact). Ideally I would want it to be dynamic; that is, not having to manually write each course in the SELECT clause.

Note 2: This needs to mysql pure - I don't want to resort to PHP.

The database currently stands at 10000+ students with 10000+ * 4 enrollments (as there is exactly 4 courses, and every student is in all 4 modules).

Note 3: Student.user_id is indexed and so is enrollment.enrollment_id, enrollment.student_id, and enrollment.course_id.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Chris
  • 54,599
  • 30
  • 149
  • 186

1 Answers1

11
select s.id,s.name,
max(case when e.course_id = 55 then complete else null end) as c55,
max(case when e.course_id = 66 then complete else null end) as c66,
max(case when e.course_id = 77 then complete else null end) as c77
from student as s
left join enrollment as e
on s.id = e.student_id
group by s.id

@Chris. Using stored procedure you could even create dynamic pivot table without knowing before the number of columns. This is the link

http://forum.html.it/forum/showthread.php?s=&threadid=1456236

of an answer of mine on an italian forum to a similar problem. There is a complete example that could help you to understand the logic behind. :)

EDIT. Update with a MYSQL DYNAMIC VIEW

This is my starting dump:

/*Table structure for table `student` */

drop table if exists `student`;

create table `student` (
  `id` int(10) unsigned not null auto_increment,
  `name` varchar(50) default null,
  primary key (`id`)
) engine=myisam;

/*Data for the table `student` */

insert  into `student`(`id`,`name`) values (1,'chris');
insert  into `student`(`id`,`name`) values (2,'joe');
insert  into `student`(`id`,`name`) values (3,'jack');

drop table if exists enrollment;

create table `enrollment` (
  `enrollment_id` int(11) auto_increment primary key,
  `student_id` int(11) default null,
  `course_id` int(11) default null,
  `complete` varchar(50) default null
) engine=myisam auto_increment=8 default charset=latin1;

/*Data for the table `enrollment` */

insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (1,1,55,'true');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (2,1,66,'true');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (3,1,77,'true');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (4,2,55,'true');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (5,2,66,'false');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (6,3,55,'false');
insert  into `enrollment`(`enrollment_id`,`student_id`,`course_id`,`complete`) values (7,3,66,'true');

and this is the stored procedure for the dynamic view:

delimiter //
drop procedure if exists dynamic_view//
create procedure dynamic_view()
begin
declare finish int default 0;
declare cid int;
declare str varchar(10000) default "select s.id,s.name,";
declare curs cursor for select course_id from enrollment group by course_id;
declare continue handler for not found set finish = 1;
open curs;
my_loop:loop
fetch curs into cid;
if finish = 1 then
leave my_loop;
end if;
set str = concat(str, "max(case when e.course_id = ",cid," then complete else null end) as course_",cid,",");
end loop;
close curs;
set str = substr(str,1,char_length(str)-1);
set @str = concat(str," from student as s
            left join enrollment as e
            on s.id = e.student_id
            group by s.id");
prepare stmt from @str;
execute stmt;
deallocate prepare stmt;
-- select str;
end;//
delimiter ;

Now let's call it

mysql> call dynamic_view();
+----+-------+-----------+-----------+-----------+
| id | name  | course_55 | course_66 | course_77 |
+----+-------+-----------+-----------+-----------+
|  1 | chris | true      | true      | true      |
|  2 | joe   | true      | false     | NULL      |
|  3 | jack  | false     | true      | NULL      |
+----+-------+-----------+-----------+-----------+
3 rows in set (0.00 sec)

Query OK, 0 rows affected (0.05 sec)

Now we insert other two records with two different courses:

insert  into `enrollment`(`student_id`,`course_id`,`complete`) values (1,88,'true');
insert  into `enrollment`(`student_id`,`course_id`,`complete`) values (3,99,'true');

and we recall the procedure. This is the result:

mysql> call dynamic_view();
+----+-------+-----------+-----------+-----------+-----------+-----------+
| id | name  | course_55 | course_66 | course_77 | course_88 | course_99 |
+----+-------+-----------+-----------+-----------+-----------+-----------+
|  1 | chris | true      | true      | true      | true      | NULL      |
|  2 | joe   | true      | false     | NULL      | NULL      | NULL      |
|  3 | jack  | false     | true      | NULL      | NULL      | true      |
+----+-------+-----------+-----------+-----------+-----------+-----------+
3 rows in set (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

That's all. :)

Nicola Cossu
  • 54,599
  • 15
  • 92
  • 98
  • @nick That is fantastic, I wasn't aware of the case when clause. It works great though, I will read up about it over the weekend (I always like to understand something, rather than just plugging in code). The dynamic pivot table is really interesting as well, I'll google translate it, and try to give it a read. An answer as elegant as this is exactly why SO is so useful. – Chris Apr 29 '11 at 00:47
  • Hi Chris. As soon as I can I will update my answer based on your example with a dynamic version :) – Nicola Cossu Apr 29 '11 at 00:48
  • Hi Nick, that would be great if you could, but if you don't have time, thats fine too. Thanks again – Chris Apr 29 '11 at 01:02
  • Done. It was exactly the same thing of the italian thread. ;) – Nicola Cossu Apr 29 '11 at 01:16