Раз мы согласились, что отношение — это множество, нам автоматически достаются три операции из теории множеств: объединение (∪), пересечение (∩) и разность (\). В SQL они называются UNION, INTERSECT, EXCEPT — и используются чаще, чем кажется.
Union compatibility — главное правило
Все три операции требуют, чтобы оба отношения были совместимы:
- Одинаковое число атрибутов.
- Соответствующие атрибуты — совместимых типов (text с text, integer с integer, и так далее).
Имена атрибутов могут отличаться — итоговое отношение наследует имена от левого операнда. Это часто удивляет новичков.
Несовместимые схемы — синтаксическая ошибка. PostgreSQL не «угадает» и не сделает implicit cast там, где число колонок не совпадает.
∪ — UNION (объединение)
UNION — это «всё, что есть в любом из двух отношений». В терминах множеств: A ∪ B.
Все кортежи, которые встречаются хотя бы в одном из двух отношений. Дубли убраны (если не UNION ALL).
Имена клиентов из RU ∪ имена клиентов из DE:
В этом конкретном запросе UNION даёт тот же результат, что и WHERE country IN ('RU','DE') — потому что нет пересечения по странам. Но представь, что одна выборка идёт из customers, а вторая — из совершенно другой таблицы (например, подписчиков рассылки). Тогда IN уже не сработает — а UNION объединит результаты двух запросов и устранит возможные дубликаты.
UNION vs UNION ALL — снова про bag и set
Помнишь, как SELECT без DISTINCT отличается от чистой π? То же самое здесь.
UNION— это множественное объединение: дубликаты удаляются. Это правильное A ∪ B с точки зрения теории.UNION ALL— это мультимножественное объединение: ничего не удаляется, просто склейка результатов «как есть».
UNION стоит дороже, чем UNION ALL, потому что СУБД должна устранить дубликаты — это либо сортировка, либо построение hash-таблицы. На больших выборках разница может быть в 2–10 раз.
Сравни: UNION выдаст уникальные страны, UNION ALL — со всеми повторами
Получим 6 строк (3 + 3, с повторами). Замени UNION ALL на UNION — получишь столько строк, сколько уникальных стран среди первых трёх клиентов (скорее всего 1–2).
Правило практики: если ты знаешь, что дубликатов быть не может (например, объединяешь две выборки по непересекающимся условиям), пиши UNION ALL — это быстрее. Если может быть пересечение, или ты не уверен, — UNION подстрахует.
∩ — INTERSECT (пересечение)
INTERSECT — это «кортежи, которые есть в обоих отношениях». A ∩ B.
Клиенты, у которых есть и delivered, и paid заказы:
Здесь видно, как INTERSECT экономит синтаксис: альтернатива — два под-запроса с WHERE customer_id IN (...) и AND-ом между ними.
\ — EXCEPT (разность)
EXCEPT — это «кортежи из A, которых нет в B». A \ B. В Oracle и MySQL та же операция называется MINUS.
Классический сценарий: «найди клиентов, которые ни разу не покупали».
Клиенты без заказов: все клиенты минус те, кто что-то заказывал
В нашей вселенной таких клиентов мало (или нет — потому что seed-данные подобраны плотно), но в реальных базах это типичный запрос для маркетинговой воронки: «кому отправить промокод на первый заказ».
Альтернатива через NOT EXISTS или LEFT JOIN ... WHERE x IS NULL обычно эффективнее на больших объёмах, но EXCEPT гораздо читаемее и ближе к человеческому языку: «всех минус тех».
Что произойдёт с NULL
Set-операции в SQL — единственное место, где NULL = NULL ведёт себя как истинное равенство. Это исключение из общего правила трёхзначной логики, сделанное специально для UNION/INTERSECT/EXCEPT (а также для DISTINCT и GROUP BY). Если у двух строк все атрибуты равны и где-то есть NULL — для этих операций они считаются дублями.
Это удобно — иначе сравнивать строки с NULL было бы невозможно. Просто помни про этот «локальный» отказ от трёхзначной логики; в модуле 3 разберём подробно.
Чек-лист
UNION,INTERSECT,EXCEPT— это ∪, ∩, \ из теории множеств.- Требование union compatibility: одинаковое число столбцов, совместимые типы.
UNIONустраняет дубли (set-семантика).UNION ALLне устраняет (bag-семантика), и дешевле.EXCEPT(в Oracle —MINUS) — компактный способ написать «всё, кроме того, что есть в B».- В set-операциях
NULLведёт себя как равный самому себе — это исключение из общей трёхзначной логики.