Введение
UNION, INTERSECT, EXCEPT (в некоторых СУБД — MINUS) — операции теоретико-множественного характера, объединяющие или сравнивающие результаты двух (или более) отдельных SELECT-запросов, а не таблиц напрямую — в статье разберём каждую из них и условия, которые должны выполняться для их корректного применения.
Концепция
UNION объединяет результаты двух запросов в один, устраняя дублирующиеся строки по умолчанию (UNION ALL сохраняет дубли, что заметно эффективнее, если дубли заведомо невозможны или не важны). INTERSECT возвращает только строки, присутствующие в результатах обоих запросов одновременно. EXCEPT/MINUS возвращает строки из первого запроса, отсутствующие в результате второго. Все три операции требуют, чтобы оба объединяемых запроса возвращали одинаковое количество столбцов с совместимыми типами данных — имена столбцов в итоговом результате берутся из первого запроса.
Пример кода: UNION, INTERSECT, EXCEPT
-- UNION — объединение клиентов и поставщиков в единый список контактов, без дублей
SELECT name, email, 'customer' AS contact_type FROM customers
UNION
SELECT name, email, 'supplier' AS contact_type FROM suppliers;
-- UNION ALL — заметно эффективнее, когда дубли заведомо не нужно устранять
SELECT order_date, total_amount FROM orders WHERE order_date >= '2026-01-01'
UNION ALL
SELECT order_date, total_amount FROM archived_orders WHERE order_date >= '2026-01-01';
-- INTERSECT — клиенты, которые есть и в текущей рассылке, и совершили покупку
SELECT customer_id FROM newsletter_subscribers
INTERSECT
SELECT customer_id FROM orders;
-- EXCEPT — клиенты из рассылки, которые НИКОГДА не совершали покупку
SELECT customer_id FROM newsletter_subscribers
EXCEPT
SELECT customer_id FROM orders;
Пояснения к коду
Добавление литерального столбца 'customer' AS contact_type/'supplier' AS contact_type в каждый из объединяемых через UNION запросов — практичный паттерн, позволяющий после объединения в единый результат различать, из какого исходного запроса (и, соответственно, исходной таблицы) пришла каждая конкретная строка, что было бы невозможно определить из самого объединённого результата без этого явного, добавленного маркера. Сравнение INTERSECT/EXCEPT для одной и той же пары запросов (подписчики рассылки и покупатели) показывает, как одни и те же два набора данных могут анализироваться с дополняющих друг друга точек зрения — «пересечение» даёт активных, конвертировавшихся подписчиков, а «разность» — потенциальную аудиторию для дополнительного стимулирования к первой покупке.
Подводные камни
- Использование
UNION(с устранением дублей) там, где дубли заведомо невозможны или несущественны, что заставляет СУБД выполнять дополнительную, не бесплатную работу по сравнению строк для устранения дублей — если по структуре запроса дубли точно не могут возникнуть (например, объединяются заведомо непересекающиеся по диапазону дат наборы данных, как во втором примере),UNION ALLдаёт идентичный по содержанию результат значительно быстрее, без дополнительных накладных расходов на проверку дублей. - Несовпадение типов или количества столбцов между объединяемыми запросами, что приводит либо к явной ошибке (несовпадение количества столбцов), либо к неявному, потенциально нежелательному преобразованию типов (если типы совместимы, но различны, например
INTEGERиVARCHAR, СУБД может привести оба к общему типу неожиданным для разработчика образом) — перед использованием этих операций важно явно проверить совместимость структуры результатов обоих объединяемых запросов. - Использование
ORDER BYвнутри одного из объединяемых подзапросов вместо применения его один раз к итоговому, объединённому результату — в большинстве СУБДORDER BYвнутри отдельной частиUNION(до самой операцииUNION) синтаксически не разрешён или не имеет смысла (поскольку порядок отдельной части не сохраняется после объединения), иORDER BYдолжен применяться единожды, после всей операцииUNION/INTERSECT/EXCEPT, к итоговому, уже объединённому результату. - Отсутствие поддержки
INTERSECT/EXCEPTв некоторых старых версиях/конфигурациях СУБД (исторически, например, в MySQL до версии 8.0.31 не было прямой поддержки этих операций) — для совместимости со старыми версиями такой СУБД эти операции эмулируются через эквивалентные конструкции сJOIN/NOT EXISTSили подзапросами (статья 400), что менее наглядно, но даёт тот же логический результат при отсутствии прямой поддержки нужной операции.