Условие
Даны два отношения:
PE(StudentID, Subject, Grade)— оценки по физкультуреEnglish(StudentID, Subject, Grade)— оценки по английскому
Найти ID студентов, которые придут на пересдачу хотя бы по одному из предметов (оценка < 3).
Решение
Реляционная алгебра
π_StudentID (σ_(Grade < 3) (PE))
∪
π_StudentID (σ_(Grade < 3) (English))
Сначала отбираем (σ) строки с двойками, проектируем (π) на ID, объединяем (∪).
SQL
SELECT StudentID FROM PE WHERE Grade < 3
UNION
SELECT StudentID FROM English WHERE Grade < 3;UNION уберёт дубликаты — если студент завалил оба предмета, его ID появится один раз.
Если нужно «все ID, по разу за каждый провал»
SELECT StudentID FROM PE WHERE Grade < 3
UNION ALL
SELECT StudentID FROM English WHERE Grade < 3;Если нужно «провалили оба»
-- INTERSECT — пересечение
SELECT StudentID FROM PE WHERE Grade < 3
INTERSECT
SELECT StudentID FROM English WHERE Grade < 3;Через UNION ALL + GROUP BY
Альтернатива INTERSECT без него (для СУБД, не поддерживающих):
SELECT StudentID
FROM (
SELECT StudentID FROM PE WHERE Grade < 3
UNION ALL
SELECT StudentID FROM English WHERE Grade < 3
) t
GROUP BY StudentID
HAVING COUNT(DISTINCT 1) >= 2; -- хотя бы из двух источниковЛибо проще через FULL JOIN.
Кому где живётся легче
| Подход | Плюсы | Минусы |
|---|---|---|
UNION |
Самое короткое, читается | Скрытый DISTINCT — тормозит на больших объёмах |
UNION ALL + GROUP BY |
Контроль | Длиннее |
| Подзапрос с OR в JOIN | Подходит, когда столбцы разные | Сложнее писать |
Подводные камни
UNIONvsUNION ALL— первое дедуплицирует. Второе быстрее.- Только
StudentID— не выбирайте*, иначе строки не дедуплицируются (Subject разный). - Граница:
Grade < 3— это 1 и 2.<= 2эквивалентно для целочисленных оценок. - NULL Grade — попадёт в выборку или нет?
NULL < 3этоUNKNOWN, неTRUE— не попадёт. Если NULL значит «нет оценки = провал», нужноGrade IS NULL OR Grade < 3.
Эталонный ответ
π_StudentID (σ_(Grade<3) (PE)) ∪ π_StudentID (σ_(Grade<3) (English))
SELECT StudentID FROM PE WHERE Grade < 3
UNION
SELECT StudentID FROM English WHERE Grade < 3;