Учащиеся проваливают предмет, если у них есть ненулевая оценка <50, по крайней мере, для одного предложения предмета по курсу</p>
Один из многих возможных способов:
SELECT id, p.name
FROM (
SELECT s.id
FROM students s
CROSS JOIN relevant_subjects rs
GROUP BY s.id
HAVING bool_and( EXISTS(
SELECT -- empty list
FROM course_enrolments ce
JOIN courses c ON c.id = ce.course
WHERE ce.mark < 50 -- also implies NOT NULL
AND ce.student = s.id
AND c.subject = rs.id
)
) -- all failed
) sub
JOIN people p USING (id);
Формирование картеанского произведения студентов и соответствующих предметов.
Агрегировать по учащимся (s.id
) и отфильтровать тех, кто провалил все предметов в предложении HAVING
с bool_and()
в коррелированном EXISTS
тестировании подзапроса, по крайней мере, дляодин такой неудачный курс для каждой комбинации студент-предмет.
Присоединитесь к people
в качестве последнего косметического шага, чтобы получить имена учеников. Я добавил id
, чтобы получить уникальные результаты (поскольку имена , вероятно, не гарантированно будут уникальными).
В зависимости от фактического определения таблицы, ваша версия Postgres,кардинальности и распределения значений, могут быть (намного) более эффективные запросы.
Это случай реляционного деления в его ядре. См .:
И самая эффективная стратегия - исключить как можно больше студентовкак можно раньше в запросе, как, например, сначала проверяя предмет с наименьшим количеством учеников-неудачников. Затем продолжите работу только с оставшимися учащимися и т. Д.
Ваш случай добавляет особую сложность, заключающуюся в том, что количество и идентичность испытуемых предметов неизвестны / динамичны. Как правило, рекурсивный CTE или аналогичный предлагает лучшую производительность для такого рода проблем: