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 (декартово произведение — намеренное). Разберём их в следующем уроке.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике