INTERSECT vs EXCEPT в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это знать
INTERSECT и EXCEPT — малоизвестные операторы SQL, которые экономят 10 строк кода при правильном использовании. На собесе для middle-аналитика могут спросить: «как найти пользователей, которые есть в обеих таблицах без JOIN». Ответ — INTERSECT.
Большинство аналитиков пользуются INNER JOIN + DISTINCT вместо INTERSECT. Работает, но длиннее и иногда медленнее. Знание set-операций = признак продвинутого SQL-скилла.
В статье:
- Короткий ответ
- INTERSECT — пересечение
- EXCEPT / MINUS — разность
- UNION — объединение
- Примеры использования
- Эквивалентные записи через JOIN
Короткий ответ
- UNION: все уникальные значения из обеих
- INTERSECT: только те, что есть в обеих
- EXCEPT (MINUS): те, что в первой, но нет во второй
Работают как set operations — автоматически дедуплицируют.
INTERSECT
Пересечение — значения в обеих таблицах:
SELECT email FROM users_old
INTERSECT
SELECT email FROM users_new;Вернёт emails, которые есть и в старой базе, и в новой.
Пример
-- пользователи, купившие И товар A, И товар B
SELECT user_id FROM orders WHERE product_id = 'A'
INTERSECT
SELECT user_id FROM orders WHERE product_id = 'B';EXCEPT
Разность — в первой, но не во второй:
SELECT email FROM users
EXCEPT
SELECT email FROM unsubscribed;Вернёт emails пользователей, которые не отписались.
Пример
-- товары, которые есть в каталоге, но не продавались в 2026
SELECT product_id FROM products
EXCEPT
SELECT DISTINCT product_id FROM orders WHERE created_at >= '2026-01-01';В Oracle
SELECT ... FROM t1
MINUS
SELECT ... FROM t2;MINUS = EXCEPT.
UNION
SELECT email FROM customers
UNION
SELECT email FROM vendors;UNION vs UNION ALL
UNION -- уникальные (дедупликация, медленнее)
UNION ALL -- всё (с дублями, быстрее)Требования
Для всех set-операций:
- Одинаковое количество колонок
- Совместимые типы (или CAST)
- Порядок колонок важен (сравниваются позиционно, не по имени)
-- ошибка — разное количество колонок
SELECT a, b FROM t1 INTERSECT SELECT a FROM t2;
-- ошибка типов
SELECT name FROM t1 INTERSECT SELECT id FROM t2; -- string vs intЭквиваленты через JOIN
INTERSECT через INNER JOIN
SELECT DISTINCT a.email
FROM users_old a
INNER JOIN users_new b ON b.email = a.email;EXCEPT через LEFT JOIN
SELECT DISTINCT a.email
FROM users a
LEFT JOIN unsubscribed b ON b.email = a.email
WHERE b.email IS NULL;EXCEPT через NOT EXISTS
SELECT DISTINCT email FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM unsubscribed WHERE email = u.email
);Когда set-операции лучше JOIN
Читаемость
-- Clear intent
SELECT product_id FROM catalog
EXCEPT
SELECT product_id FROM sold_products;
-- vs
SELECT c.product_id FROM catalog c
LEFT JOIN sold_products s ON s.product_id = c.product_id
WHERE s.product_id IS NULL;Multi-column comparison
SELECT user_id, product_id FROM purchases_old
INTERSECT
SELECT user_id, product_id FROM purchases_new;JOIN эквивалент — 3 строки более.
Auto-deduplication
INTERSECT / EXCEPT сразу убирают дубликаты. JOIN + DISTINCT.
Performance
- INTERSECT / EXCEPT обычно медленнее INNER JOIN / NOT EXISTS, но разница маленькая
- Для больших данных — optimizer может переписать в JOIN
- Используйте то, что читается чище
В разных СУБД
| Postgres | MySQL | MSSQL | Oracle | |
|---|---|---|---|---|
| UNION | ✓ | ✓ | ✓ | ✓ |
| INTERSECT | ✓ | ✓ (8.0+) | ✓ | ✓ |
| EXCEPT | ✓ | ✓ (8.0+) | ✓ | MINUS |
В MySQL до 8.0 — только через NOT IN / NOT EXISTS.
Практические use cases
Миграция данных
-- какие users в старой базе, но не в новой (нужно смигрировать)
SELECT user_id FROM old_users
EXCEPT
SELECT user_id FROM new_users;A/B-test audience
-- пользователи в test, но не в control (не должно быть)
SELECT user_id FROM test_group
INTERSECT
SELECT user_id FROM control_group;Должно быть 0. Если не 0 — sample contamination.
Cross-sell analysis
-- купили A, но не B
SELECT DISTINCT user_id FROM orders WHERE product = 'A'
EXCEPT
SELECT DISTINCT user_id FROM orders WHERE product = 'B';Частые ошибки
1. INTERSECT ALL / EXCEPT ALL
В Postgres есть INTERSECT ALL / EXCEPT ALL — сохраняют дубликаты. Обычно не нужно.
2. NULL handling
NULL = NULL → UNKNOWN. В set-операциях NULL сравниваются как равные. Это отличие от JOIN.
-- в INTERSECT NULL=NULL считаются равными
SELECT NULL INTERSECT SELECT NULL; -- возвращает NULL ROW3. Порядок колонок
Сравниваются позиционно. (a, b) INTERSECT (b, a) — разные результаты.
4. В MySQL < 8.0
Нет INTERSECT / EXCEPT. Используйте NOT EXISTS / INNER JOIN.
Связанные темы
FAQ
INTERSECT или INNER JOIN?
Functionally эквивалентны. INTERSECT короче для simple case.
EXCEPT с NULL?
В set-операциях NULL считаются равными между собой. Отличие от JOIN.
MySQL старых версий?
Используйте NOT IN / NOT EXISTS.
Performance?
Обычно на уровне JOIN. Optimizer переписывает.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.