LEFT JOIN и RIGHT JOIN
LEFT JOIN и RIGHT JOIN
В предыдущем уроке INNER JOIN возвращал только строки с совпадением в обеих таблицах. Но во многих практических задачах нужно сохранить все строки из одной таблицы, даже если для них нет совпадения в другой. Именно для этого существуют LEFT JOIN и RIGHT JOIN — внешние соединения.
LEFT JOIN: сохранить все строки левой таблицы
LEFT JOIN (или LEFT OUTER JOIN) возвращает все строки из левой таблицы (той, что указана до LEFT JOIN). Для строк, которым нашлась пара в правой таблице, колонки правой таблицы заполняются значениями. Для строк без пары — колонки правой таблицы заполняются NULL.
SELECT c.name, o.product, o.amount
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;
Таблица customers (3 клиента, у Веры нет заказов):
name | product | amount
-------------+----------+-------
Анна Иванова | Ноутбук | 89990
Анна Иванова | Наушники | 7800
Борис Петров | Мышь | 1200
Вера Сидорова| NULL | NULL
Вера теперь в результате — с NULL в колонках заказа. LEFT JOIN гарантирует: ни одна строка из левой таблицы не будет потеряна.
Типичный сценарий: найти строки без пары
Самое частое применение LEFT JOIN — найти строки, для которых нет соответствующей строки в другой таблице. Например, клиенты без заказов:
SELECT c.name, c.email
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.id IS NULL;
Логика: LEFT JOIN оставляет всех клиентов. У клиентов без заказов o.id будет NULL. WHERE o.id IS NULL фильтрует именно таких клиентов.
Этот паттерн «LEFT JOIN + WHERE правая.ключ IS NULL» — стандартный способ найти «сиротские» строки.
Проверь себя: что вернёт тот же запрос, если заменить WHERE o.id IS NULL на WHERE o.id IS NOT NULL?
RIGHT JOIN: сохранить все строки правой таблицы
RIGHT JOIN — зеркальный вариант LEFT JOIN. Сохраняются все строки из правой таблицы (той, что указана после RIGHT JOIN). Левая таблица дополняет их данными, а при отсутствии совпадения ставятся NULL.
SELECT c.name, o.product, o.amount
FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id;
Если в orders есть заказ с несуществующим customer_id:
name | product | amount
-------------+------------+-------
Анна Иванова | Ноутбук | 89990
Анна Иванова | Наушники | 7800
Борис Петров | Мышь | 1200
NULL | Загадочный | 500
«Загадочный» товар — заказ без клиента — появился в результате. Его customer_id ссылается на несуществующую запись, и c.name равен NULL.
RIGHT JOIN на практике: почему его редко используют
RIGHT JOIN логически идентичен LEFT JOIN с переставленными таблицами. Любой RIGHT JOIN можно переписать как LEFT JOIN, просто поменяв порядок таблиц:
-- Эти два запроса дают одинаковый результат:
SELECT c.name, o.product FROM customers c RIGHT JOIN orders o ON c.id = o.customer_id;
SELECT c.name, o.product FROM orders o LEFT JOIN customers c ON c.id = o.customer_id;
Большинство разработчиков предпочитают LEFT JOIN — он читается естественнее: «все из A, дополненные данными из B». RIGHT JOIN встречается редко и часто вызывает путаницу. Если видите RIGHT JOIN в коде — мысленно переверните порядок таблиц и представьте его как LEFT JOIN.
Разница между LEFT JOIN и INNER JOIN
Понять разницу помогает один вопрос: «что будет с строками без пары?»
-- INNER JOIN: только строки с совпадением в обеих таблицах
SELECT c.name, COUNT(o.id) AS orders_count
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;
-- Результат: 2 строки (Анна и Борис, у Веры нет заказов)
-- LEFT JOIN: все клиенты, даже без заказов
SELECT c.name, COUNT(o.id) AS orders_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;
-- Результат: 3 строки (Анна, Борис, Вера с 0 заказов)
COUNT(o.id) в LEFT JOIN-варианте вернёт 0 для Веры, потому что COUNT(col) не считает NULL. Это идиоматический способ получить количество связанных записей, включая ноль.
NULL в колонках LEFT JOIN: важная деталь
При LEFT JOIN все колонки правой таблицы для «несовпавших» строк будут NULL. Это может влиять на агрегатные функции и условия WHERE:
-- Неправильно: WHERE o.status = 'delivered' исключит клиентов без заказов,
-- превращая LEFT JOIN в INNER JOIN по поведению
SELECT c.name, o.product
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.status = 'delivered';
-- Правильно: перенести условие на правую таблицу в ON
SELECT c.name, o.product
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id AND o.status = 'delivered';
Во втором варианте условие o.status = 'delivered' — часть правила соединения, а не фильтр результата. Клиенты без «delivered» заказов останутся в результате с NULL в колонке o.product.
Это тонкое, но важное различие: условие в ON влияет на то, какие строки правой таблицы считаются «совпадением», а условие в WHERE фильтрует строки уже после соединения.
Практический пример: отчёт по клиентам
Задача: показать всех клиентов — количество заказов и сумму трат. Клиенты без заказов должны показываться с нулями, а не исчезать.
SELECT c.id,
c.name,
c.email,
COUNT(o.id) AS orders_count,
COALESCE(SUM(o.amount), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name, c.email
ORDER BY total_spent DESC;
COALESCE(SUM(o.amount), 0) заменяет NULL на 0 для клиентов без заказов. Без COALESCE был бы NULL — в отчёте выглядит некрасиво и может ломать расчёты.
Визуализация: LEFT JOIN vs INNER JOIN
Полезная аналогия — два списка с подчёркнутыми строками. INNER JOIN отдаёт только строки, которые есть в обоих списках. LEFT JOIN отдаёт весь левый список, дополняя его данными из правого там, где совпадение нашлось.
Левая (customers): Правая (orders):
1 - Анна ───────────────→ 1 - Ноутбук ✓ совпадение
1 - Анна ───────────────→ 2 - Наушники ✓ совпадение
2 - Борис ──────────────→ 3 - Мышь ✓ совпадение
3 - Вера ──────────────→ (нет строк) NULL
LEFT JOIN результат: Анна×2, Борис×1, Вера с NULL
INNER JOIN результат: Анна×2, Борис×1 (Вера отсутствует)
Когда выбирать LEFT JOIN, а когда INNER JOIN
Используйте LEFT JOIN, когда:
- Нужен полный список из одной таблицы с добавочными данными (пусть и пустыми)
- Задача звучит как «все X, у которых есть/нет Y»
- Статистика должна включать нули (клиенты с 0 заказами, студенты без оценок)
Используйте INNER JOIN, когда:
- Вас интересуют только строки, у которых данные в обеих таблицах существуют
- Связь «гарантированная» (например, каждый заказ имеет клиента по ограничению FK)
- Аналитика по существующим данным (выручка по заказам — не нужны клиенты без заказов)
Краткий итог
LEFT JOINвозвращает все строки левой таблицы; при отсутствии совпадения —NULLиз правойRIGHT JOIN— зеркальный вариант; на практике предпочитайтеLEFT JOINс переставленными таблицами- Паттерн «
LEFT JOIN+WHERE правая.ключ IS NULL» — находит строки без пары - Условие в
ONвлияет на то, что считается совпадением;WHEREфильтрует после соединения COUNT(o.id)приLEFT JOINсчитаетNULLкак 0; используйте для «0 или больше» агрегатов
Проверка данных через LEFT JOIN
LEFT JOIN активно используется не только для аналитики, но и для аудита данных. Несколько практических паттернов:
Найти записи без связанных данных:
-- Продукты, которые ни разу не заказывали
SELECT p.id, p.title
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.id IS NULL;
Найти «осиротевшие» строки (orphaned rows):
-- Заказы, у которых нет клиента (нарушение ссылочной целостности)
SELECT o.id, o.customer_id, o.amount
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE c.id IS NULL;
Такие запросы — стандартный инструмент проверки качества данных в любой системе.
Что дальше
Вы освоили LEFT JOIN и RIGHT JOIN. Есть ещё два специальных вида соединений: FULL OUTER JOIN (сохраняет все строки из обеих таблиц) и CROSS JOIN (декартово произведение — намеренное). Разберём их в следующем уроке.