Условие
В customers(id, name) хранятся все клиенты, в orders(id, customer_id, ts) — их заказы. Выведите имена клиентов, у которых нет ни одного заказа.
Решение
Подход
Три эквивалентных способа: LEFT JOIN ... WHERE orders.id IS NULL, NOT EXISTS, NOT IN. В реальной жизни оптимизаторы Postgres и MySQL хорошо переписывают первые два в анти-джойн. NOT IN опасен из-за NULL.
Реализация
-- LEFT JOIN + IS NULL
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
-- NOT EXISTS (часто самый читаемый)
SELECT c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
-- NOT IN — осторожно с NULL
SELECT c.name
FROM customers c
WHERE c.id NOT IN (
SELECT customer_id FROM orders WHERE customer_id IS NOT NULL
);Подводные камни
NOT IN+ NULL. Если подзапрос вернёт хотя бы одинNULL, весьNOT INдаст пустой результат — из-за трёхзначной логики. Всегда фильтруйтеIS NOT NULLвнутри подзапроса или используйтеNOT EXISTS.LEFT JOINс фильтром по правой таблице вWHERE— типичная ошибка: фильтрация вWHEREпревращаетLEFT JOINвINNER JOIN. Условие проверки наIS NULLкорректно только для колонок правой таблицы.- Дубликаты клиентов в результате: если у клиента «нет заказов» и таблица
customersуникальна поid,LEFT JOINдаст одну строку. Но если вcustomersдубликаты — поможетDISTINCT. - На больших таблицах
NOT EXISTSчасто работает быстрееLEFT JOIN + IS NULLблагодаря early-exit.
Эталонный ответ
LEFT JOIN orders ... WHERE o.id IS NULL или NOT EXISTS (SELECT 1 FROM orders WHERE customer_id = c.id). NOT IN без IS NOT NULL ломается на NULL-ах.