Аналогично ответу Прели, я бы использовал левое соединение, чтобы выделить экзамены, которые не были сданы:
SELECT s.code, s.name, p.course
FROM
students s
INNER JOIN
programs p
ON (p.degree = s.degree)
Это список экзаменов, которые студент должен сдать на курс
Теперь добавьте к экзаменам, которые они сдали, и оставьте нули для тех, кого у них нет:
SELECT s.code, s.name, p.course, e.course
FROM
students s
INNER JOIN
programs p
ON (p.degree = s.degree)
LEFT OUTER JOIN
exams e
ON e.student = s.code AND e.course = p.course
Результаты будут выглядеть так:
Code | Name | p.Course | e.Course
stu001 | John | Calc A | Calc A
stu001 | John | Calc B | Calc B
stu002 | Doe | Calc A | <null>
stu002 | Doe | Calc B | <null>
Теперь сократите список учеников, сдавших экзамен по каждому курсу. Мы можем сделать это, проверив, что COUNT (e.course) совпадает с COUNT (p.course), поскольку COUNT () не учитывает NULL, поэтому любые вхождения нуля (без экзамена) в e.course уменьшают count, по сравнению с подсчетом общего числа случаев p.course (я мог бы также использовать count (*)):
SELECT s.code, s.name
FROM
students s
INNER JOIN
programs p
ON (p.degree = s.degree)
LEFT OUTER JOIN
exams e
ON e.student = s.code AND e.course = p.course
GROUP BY s.code, s.name
HAVING COUNT(p.course) = COUNT(e.course)
УЧЕТ Джона (p.Course) будет равен 2, а COUNT (e.course) также будет 2, поэтому он показывает (только один раз, потому что он сгруппирован. COUNT у Доу (p.course) равен 2, но COUNT (e. Конечно) равен 0, потому что все значения равны нулю, а 2! = 0, поэтому он скрыт