Есть таблицы customers и orders. Напишите запрос: клиенты, не сделавшие ни одного заказа за последние 90 дней. Чем LEFT JOIN отличается от INNER JOIN?
Короткий ответ
- INNER JOIN возвращает только совпавшие строки обеих таблиц
- LEFT JOIN сохраняет все строки левой таблицы
- Несовпавшие строки справа заполняются NULL
- Анти-джойн: LEFT JOIN плюс фильтр IS NULL
- Условие по дате ставится в ON, не в WHERE
- Альтернатива — NOT EXISTS, часто читается яснее
Задача решается анти-джойном (LEFT JOIN с проверкой IS NULL) или NOT EXISTS, а ключевая ловушка — условие по дате должно стоять в ON, иначе LEFT JOIN вырождается в INNER.
Как сказать вслух
пример ответаINNER JOIN вернёт только те строки, где нашлось соответствие в обеих таблицах, а LEFT JOIN сохранит всех клиентов, подставив NULL там, где заказов нет. Для задачи я делаю LEFT JOIN заказов за девяносто дней и отбираю строки, где заказ NULL — это называется анти-джойн. Важная тонкость: условие по дате нужно писать в ON, а не в WHERE, иначе фильтр по полю правой таблицы убьёт строки с NULL и LEFT JOIN превратится в INNER. Ещё это красиво решается через NOT EXISTS.
Подробный ответ
Основной ответ
INNER JOIN возвращает пересечение: строки, для которых условие соединения выполнилось в обеих таблицах. LEFT JOIN возвращает все строки левой таблицы; если справа совпадения нет, её столбцы заполняются NULL. На этом строится анти-джойн: соединяем клиентов с их заказами за период и оставляем строки, где заказа не нашлось (o.id IS NULL). Классическая ловушка: если условие «за последние 90 дней» написать в WHERE (where o.created_at >= ...), то строки с NULL в дате отфильтруются и LEFT JOIN фактически станет INNER — клиенты без заказов пропадут из результата. Поэтому фильтры по правой таблице при анти-джойне пишутся в ON. Эквивалентное и часто более читаемое решение — NOT EXISTS с коррелированным подзапросом; современные оптимизаторы выполняют оба варианта одинаково эффективно.
Ключевые моменты
- ON против WHERE. При LEFT JOIN условие по правой таблице в WHERE отбрасывает NULL-строки и превращает соединение во внутреннее.
- Анти-джойн. LEFT JOIN с фильтром IS NULL по ключу правой таблицы возвращает строки без соответствия.
- NOT EXISTS. Альтернатива анти-джойну; предпочтительнее NOT IN, который ломается на NULL в подзапросе.
- Прочие JOIN. RIGHT — зеркален LEFT, FULL — объединение с NULL с обеих сторон, CROSS — декартово произведение.
Практический контекст
SQL-задачи такого типа дают почти на каждом собеседовании аналитика: live-coding или разбор на доске. Интервьюер провоцирует именно на ошибку с WHERE при LEFT JOIN — это главный проверочный момент задачи. В работе такие запросы аналитик пишет постоянно: проверка данных перед миграцией, сверка интеграций, ответы бизнесу «сколько клиентов неактивны».
Пример кода
-- Вариант 1: анти-джойн
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.created_at >= CURRENT_DATE - INTERVAL '90 days'
WHERE o.id IS NULL;
-- Вариант 2: NOT EXISTS
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
AND o.created_at >= CURRENT_DATE - INTERVAL '90 days'
);Частые ошибки
- Ставят условие по дате заказа в WHERE и теряют клиентов без заказов
- Используют NOT IN с подзапросом, который возвращает NULL, и получают пустой результат
- Забывают, что LEFT JOIN может размножить строки, если справа несколько совпадений