EXCEPT (MINUS): разность множеств
Что такое EXCEPT и зачем он нужен?
EXCEPT (или MINUS в некоторых СУБД) — это оператор для работы с множествами, который возвращает только те строки из первого запроса, которых нет во втором. Это как вычитание в математике, но для таблиц!
-- Простейший пример (PostgreSQL, SQL Server)
SELECT * FROM table1
EXCEPT
SELECT * FROM table2;
-- Аналог в Oracle, MySQL
SELECT * FROM table1
MINUS
SELECT * FROM table2;
📌 Где это применяется?
- Поиск уникальных данных: например, какие товары есть в одном магазине, но отсутствуют в другом
- Анализ изменений: какие клиенты были активны в прошлом месяце, но не совершили покупок в текущем
- Очистка данных: исключение тестовых записей из основного отчёта
Как работает EXCEPT: разбираем на примерах
Пример 1: Сравнение двух списков клиентов
Допустим, у нас есть:
old_customers— клиенты за прошлый годnew_customers— клиенты за текущий год
Найдём тех, кто перестал пользоваться нашим сервисом:
SELECT customer_id, email FROM old_customers
EXCEPT
SELECT customer_id, email FROM new_customers;
💡 Важно!
- Количество и порядок столбцов в обоих запросах должны совпадать
- Типы данных должны быть совместимы (например, нельзя сравнивать VARCHAR с INTEGER)
Пример 2: EXCEPT с условиями
Добавим фильтрацию — найдём только московских клиентов, которые ушли:
SELECT customer_id, email
FROM old_customers
WHERE city = 'Москва'
EXCEPT
SELECT customer_id, email
FROM new_customers
WHERE city = 'Москва';
Нюансы, о которых важно знать
1. Обработка дубликатов
EXCEPT автоматически удаляет дубликаты в результате (как DISTINCT). Если нужно сохранить повторы — используйте UNION ALL с хитрыми условиями.
2. Сравнение NULL-значений
В SQL NULL ≠ NULL! Поэтому строки с NULL попадут в результат, даже если кажутся одинаковыми:
-- Пример с NULL (вернёт строку)
SELECT 1, NULL
EXCEPT
SELECT 1, NULL;
3. Производительность
Для больших таблиц EXCEPT может работать медленно. В таких случаях иногда эффективнее использовать NOT EXISTS:
-- Альтернатива EXCEPT
SELECT a.*
FROM table1 a
WHERE NOT EXISTS (
SELECT 1 FROM table2 b
WHERE a.id = b.id
);
Практика: решаем реальные задачи
Задача 1: Поиск «выпавших» товаров
Есть таблицы products_2023 и products_2024. Найдём товары, которые сняли с продажи:
SELECT product_id, name
FROM products_2023
EXCEPT
SELECT product_id, name
FROM products_2024;
Задача 2: Анализ активности пользователей
Какие пользователи зарегистрировались, но ни разу не зашли в систему?
SELECT user_id FROM registrations
EXCEPT
SELECT user_id FROM logins;
EXCEPT vs NOT IN vs NOT EXISTS
Когда что использовать? Краткий гид:
| Оператор | Плюсы | Минусы |
|---|---|---|
| EXCEPT | Читаемость, простота | Медленнее на больших данных |
| NOT IN | Простота синтаксиса | Проблемы с NULL |
| NOT EXISTS | Оптимизация, гибкость условий | Сложнее писать |
Пример для сравнения:
-- NOT IN (осторожно с NULL!)
SELECT id FROM table1
WHERE id NOT IN (SELECT id FROM table2);
-- NOT EXISTS (без проблем с NULL)
SELECT id FROM table1 a
WHERE NOT EXISTS (SELECT 1 FROM table2 b WHERE a.id = b.id);
Продвинутые приёмы
Комбинирование с другими операторами
EXCEPT можно сочетать с UNION и INTERSECT для сложной аналитики:
-- Найдём товары, которые были в 2023, но исчезли или изменили цену в 2024
(SELECT product_id FROM products_2023
EXCEPT
SELECT product_id FROM products_2024)
UNION
(SELECT product_id FROM products_2023
INTERSECT
SELECT product_id FROM products_2024
WHERE price <> (SELECT price FROM products_2023 p WHERE p.product_id = products_2024.product_id));
EXCEPT в CTE
Отлично работает с общими табличными выражениями:
WITH inactive_users AS (
SELECT user_id FROM all_users
EXCEPT
SELECT user_id FROM active_users
)
SELECT * FROM inactive_users
JOIN user_profiles USING (user_id);
Итоги: когда EXCEPT — идеальный выбор
✅ Нужно сравнить два набора данных "в лоб"
✅ Важна читаемость кода (EXCEPT понятнее NOT EXISTS для новичков)
✅ Работаем с небольшими/средними таблицами
✅ Нужно исключить дубликаты в результате
Для углублённого изучения операций с множествами в SQL загляните на YouTube-канал Данилы Бежина — там есть разборы реальных кейсов!