SQLJOINLEFT JOINtutorial

Что такое LEFT JOIN в SQL?

LEFT JOIN — соединение таблиц, при котором ВСЕ строки левой таблицы попадают в результат. Если справа пары нет, ставится NULL. Разбираем синтаксис, отличие от INNER JOIN, поиск 'сирот' и три задачи.

13 мин чтенияСправочникSQL · JOIN · LEFT JOIN · tutorial · beginner

LEFT JOIN — это соединение таблиц, при котором в результат попадают все строки из левой таблицы.

Если для строки из левой таблицы нашлась подходящая строка в правой таблице — SQL покажет данные из обеих таблиц. Если пары справа нет — строка всё равно останется в результате, а в колонках правой таблицы будет NULL.

Полное название — LEFT OUTER JOIN, но слово OUTER почти всегда опускают. Поэтому:

LEFT JOIN

и

LEFT OUTER JOIN

означают одно и то же.

Главная идея такая: LEFT JOIN не выбрасывает строки из основной таблицы. Он бережно сохраняет их и показывает: «вот эта строка есть, но справа для неё ничего не нашлось».

Чем LEFT JOIN отличается от INNER JOIN

INNER JOIN показывает только те строки, у которых есть совпадение в обеих таблицах.

LEFT JOIN показывает все строки из левой таблицы, даже если совпадения справа нет.

Представь школьный журнал.

Есть список учеников. Есть таблица с оценками за контрольную. Не все ученики контрольную сдали.

INNER JOIN отвечает на вопрос:

«Покажи только учеников, у которых есть оценка».

LEFT JOIN отвечает на другой вопрос:

«Покажи всех учеников. Если оценка есть — покажи её. Если оценки нет — оставь пустое место».

В реальной работе второй вопрос встречается очень часто. Нам важно видеть не только тех, у кого всё хорошо, но и тех, у кого данных нет: пользователей без заказов, товары без продаж, уроки без решений, посты без комментариев.

Зачем нужен LEFT JOIN

LEFT JOIN нужен, когда основная таблица должна сохраниться полностью.

Например:

  • показать всех пользователей и количество их заказов, включая тех, у кого заказов 0;
  • найти товары, которые ни разу не покупали;
  • найти студентов, которые не отправили домашнее задание;
  • вывести все уроки курса и количество решений по каждому;
  • показать всех клиентов, даже если у некоторых ещё нет платежей;
  • найти записи без пары в другой таблице.

Это очень важный инструмент для аналитики. Потому что бизнесу часто нужны не только успешные события, но и «дыры»: кто не купил, кто не дошёл, кто не сдал, где нет активности.

INNER JOIN такие строки просто удалит из результата. LEFT JOIN их сохранит.

Базовый синтаксис LEFT JOIN

Общая форма такая:

SELECT
  columns
FROM table_a AS a
LEFT JOIN table_b AS b
  ON a.id = b.table_a_id;

Здесь table_a — левая таблица. Она стоит после FROM.

table_b — правая таблица. Она стоит после LEFT JOIN.

Слово LEFT означает: левая таблица сохраняется полностью.

Можно читать запрос почти по-русски:

«Возьми строки из table_a. Попробуй подобрать к ним строки из table_b по условию в ON. Если справа ничего не нашлось — всё равно оставь строку из table_a, а справа поставь NULL».

Пример: ученики и оценки

Допустим, есть таблица students.

id name
1 Anna
2 Boris
3 Vera
4 Grigoriy

И есть таблица grades с оценками за тест.

id student_id score
10 1 5
11 1 4
12 2 3

Обрати внимание: в таблице оценок есть строки только для Анны и Бориса. У Веры и Григория оценок нет.

Теперь хотим получить всех учеников и их оценки, если они есть.

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id;

Результат:

name score
Anna 5
Anna 4
Boris 3
Vera NULL
Grigoriy NULL

Что здесь произошло?

Анна есть в students, и для неё нашлись две оценки в grades. Поэтому Анна появилась в результате два раза.

Борис есть в students, и для него нашлась одна оценка. Поэтому он появился один раз.

Вера и Григорий есть в students, но в grades для них ничего не нашлось. LEFT JOIN не выбросил их, а оставил в результате. Просто в колонке score стоит NULL.

Это и есть главное поведение LEFT JOIN.

Что было бы с INNER JOIN

Если заменить LEFT JOIN на INNER JOIN, результат будет другим.

SELECT
  s.name,
  g.score
FROM students AS s
INNER JOIN grades AS g
  ON s.id = g.student_id;

Результат:

name score
Anna 5
Anna 4
Boris 3

Вера и Григорий исчезли.

Почему? Потому что INNER JOIN оставляет только строки с совпадением в обеих таблицах. Нет оценки — нет строки в результате.

Иногда это именно то, что нужно. Но если задача — увидеть всех учеников, включая тех, кто не сдал тест, нужен LEFT JOIN.

Как LEFT JOIN работает по шагам

Возьмём запрос:

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id;

SQL делает примерно следующее:

  1. Берёт первую строку из students.
  2. Ищет в grades строки, где g.student_id равен s.id.
  3. Если нашёл одну строку — соединяет их.
  4. Если нашёл несколько строк — создаёт несколько строк результата.
  5. Если не нашёл ничего — всё равно оставляет строку из students, а поля из grades заполняет NULL.

Поэтому важно понимать: LEFT JOIN не гарантирует, что каждая строка левой таблицы появится ровно один раз.

Если справа найдено несколько совпадений, строка слева размножится.

Анна была одна в students, но у неё две оценки в grades, поэтому в результате две строки.

Поиск записей без пары

Один из самых полезных приёмов с LEFT JOIN — найти строки, для которых справа ничего нет.

Например, хотим найти учеников, которые не написали тест.

SELECT
  s.name
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id
WHERE g.id IS NULL;

Результат:

name
Vera
Grigoriy

Логика такая:

  1. LEFT JOIN сохранил всех учеников.
  2. У тех, кто писал тест, справа нашлись строки из grades.
  3. У тех, кто не писал тест, справа ничего не нашлось, поэтому g.id стал NULL.
  4. Фильтр WHERE g.id IS NULL оставил только строки без пары.

Это классический паттерн:

SELECT
  a.columns
FROM table_a AS a
LEFT JOIN table_b AS b
  ON a.id = b.table_a_id
WHERE b.id IS NULL;

Так ищут:

  • пользователей без заказов;
  • товары без продаж;
  • посты без комментариев;
  • уроки без решений;
  • клиентов без платежей;
  • записи, которые не прошли сверку с другой таблицей.

В таких задачах LEFT JOIN работает как мягкая проверка: «покажи мне всё слева и дай увидеть, где справа пусто».

Главная ловушка: фильтр по правой таблице в WHERE

С LEFT JOIN есть одна ошибка, на которую попадаются почти все новички.

Допустим, мы хотим показать всех учеников, но из оценок справа нас интересуют только пятёрки.

Новичок часто пишет так:

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id
WHERE g.score = 5;

Результат:

name score
Anna 5

На первый взгляд всё нормально: показали пятёрку Анны. Но мы потеряли Бориса, Веру и Григория.

Почему так произошло?

У Веры и Григория нет оценок, значит после LEFT JOIN в g.score стоит NULL.

А условие:

WHERE g.score = 5

не пропускает NULL. В SQL выражение NULL = 5 не является истиной. Поэтому строки без оценки отфильтровались.

В итоге запрос почти превратился в INNER JOIN: он оставил только тех, у кого справа есть подходящая строка.

Фильтр по правой таблице часто нужно переносить в ON

Если задача звучит так:

«Покажи всех учеников, а справа подставь только пятёрки, если они есть»,

то условие по оценке нужно поставить в ON.

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id
 AND g.score = 5;

Результат:

name score
Anna 5
Boris NULL
Vera NULL
Grigoriy NULL

Теперь все ученики остались.

Что изменилось?

Условие в ON говорит: «при соединении подбирай справа только такие оценки, где score = 5».

Если такой оценки нет, строка из students всё равно сохраняется, потому что это LEFT JOIN.

А условие в WHERE работает уже после соединения и может выбросить строку целиком.

Это очень важное отличие.

Простое правило для ON и WHERE

Правило не абсолютное на все случаи жизни, но для новичка очень полезное:

Если условие ограничивает строки правой таблицы при соединении — чаще клади его в ON.

Если условие фильтрует основную левую таблицу — чаще клади его в WHERE.

Например, хотим взять только активных учеников, но оценки справа оставить необязательными:

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id
WHERE s.is_active = true;

Здесь s.is_active = true относится к левой таблице. Мы действительно хотим убрать неактивных учеников из результата.

А если хотим показать всех активных учеников и только оценки за конкретный тест, условие по правой таблице лучше положить в ON.

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id
 AND g.test_id = 7
WHERE s.is_active = true;

Так запрос читается правильно:

  • слева берём активных учеников;
  • справа подставляем оценку за тест 7, если она есть;
  • если оценки нет — ученик всё равно остаётся.

LEFT JOIN и NULL

NULL после LEFT JOIN означает: «для этой строки слева не нашлось подходящей строки справа».

Это не ошибка. Это нормальный и очень полезный сигнал.

Например:

name score
Vera NULL

означает не «у Веры оценка равна нулю», а «у Веры нет строки с оценкой».

Это разные вещи.

0 — это значение.

NULL — это отсутствие значения.

Если ученик написал тест на 0 баллов, строка справа есть, и оценка равна 0.

Если ученик вообще не писал тест, строки справа нет, и после LEFT JOIN будет NULL.

Именно поэтому для поиска отсутствующих записей используют IS NULL.

WHERE g.id IS NULL

А не так:

WHERE g.id = NULL

В SQL с NULL нельзя сравнивать через =. Нужно использовать IS NULL или IS NOT NULL.

LEFT JOIN в интернет-магазине

Теперь посмотрим на пример ближе к реальной аналитике.

Есть таблица users.

id name signup_date
1 Anna 2024-01-15
2 Boris 2024-02-01
3 Vera 2024-02-20
4 Grigoriy 2024-03-10

И таблица orders.

id user_id amount created_at
50 1 500 2024-02-05
51 1 1500 2024-02-10
52 2 200 2024-02-25

Бизнес задаёт вопрос:

«Сколько заказов и какая общая сумма покупок у каждого пользователя? Включая тех, кто пока ничего не купил».

Здесь нужен именно LEFT JOIN.

SELECT
  u.name,
  COUNT(o.id) AS orders_count,
  COALESCE(SUM(o.amount), 0) AS total_spent
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_spent DESC;

Результат:

name orders_count total_spent
Anna 2 2000
Boris 1 200
Vera 0 0
Grigoriy 0 0

Вот ради таких строк и нужен LEFT JOIN.

Вера и Григорий ничего не покупали, но они важны для отчёта. Если бы мы использовали INNER JOIN, они бы исчезли, и картина стала бы красивее, но лживее.

Почему COUNT(o.id), а не COUNT(*)

После LEFT JOIN важно аккуратно считать строки.

Посмотрим на запрос:

SELECT
  u.name,
  COUNT(*) AS rows_count
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY u.name;

Для пользователей без заказов COUNT(*) вернёт 1, потому что строка в результате всё равно есть: слева пользователь, справа NULL.

Но это не значит, что у пользователя один заказ.

Поэтому для подсчёта реальных заказов нужно считать колонку из правой таблицы:

COUNT(o.id)

COUNT(o.id) считает только не-NULL значения. Если заказа нет, o.id равен NULL, и такая строка не попадёт в счётчик.

Сравни смысл:

COUNT(*)

считает строки результата.

COUNT(o.id)

считает найденные заказы.

После LEFT JOIN это часто разные вещи.

Почему SUM нужно оборачивать в COALESCE

В запросе выше было:

COALESCE(SUM(o.amount), 0) AS total_spent

Почему не просто SUM(o.amount)?

Потому что если у пользователя нет заказов, справа нет сумм для сложения. Для такого пользователя SUM(o.amount) вернёт NULL.

Для отчёта обычно удобнее показать 0, а не пустоту. Поэтому используют COALESCE.

COALESCE(SUM(o.amount), 0)

Это означает: «посчитай сумму, а если получился NULL, покажи 0».

Важно: мы не меняем данные в таблице. Мы просто красиво оформляем результат запроса.

LEFT JOIN для поиска товаров без продаж

Ещё один классический пример.

Есть таблица products и таблица order_items.

Нужно найти товары, которые ни разу не покупали.

SELECT
  p.id,
  p.name
FROM products AS p
LEFT JOIN order_items AS oi
  ON p.id = oi.product_id
WHERE oi.id IS NULL;

Такой запрос полезен для бизнеса:

  • найти мёртвый ассортимент;
  • понять, какие товары нужно убрать с витрины;
  • увидеть, что не продаётся;
  • проверить ошибки в каталоге;
  • найти новые товары, по которым ещё нет продаж.

LEFT JOIN здесь сохраняет все товары, а WHERE oi.id IS NULL оставляет только те, у которых не нашлось ни одной строки продаж.

LEFT JOIN не всегда нужен

Иногда начинающие начинают использовать LEFT JOIN везде «на всякий случай».

Так делать не стоит.

Если задача звучит:

«Покажи пользователей, у которых есть заказы»,

то лучше использовать INNER JOIN.

SELECT
  u.name,
  o.id AS order_id,
  o.amount
FROM users AS u
INNER JOIN orders AS o
  ON u.id = o.user_id;

Здесь пользователи без заказов не нужны. Значит, не нужно их сохранять.

LEFT JOIN нужен тогда, когда строки без пары важны для ответа.

Если строки без пары не нужны, INNER JOIN обычно точнее выражает смысл запроса.

RIGHT JOIN лучше почти не использовать

В SQL есть ещё RIGHT JOIN. Он сохраняет все строки правой таблицы.

Например:

SELECT
  s.name,
  g.score
FROM grades AS g
RIGHT JOIN students AS s
  ON s.id = g.student_id;

По смыслу это похоже на LEFT JOIN, только таблицы стоят наоборот.

Но на практике RIGHT JOIN используют редко. Обычно проще переставить таблицы местами и написать обычный LEFT JOIN.

Так читать легче:

SELECT
  s.name,
  g.score
FROM students AS s
LEFT JOIN grades AS g
  ON s.id = g.student_id;

Когда запросы становятся большими, единый стиль очень помогает. Большинство разработчиков легче читают цепочки через LEFT JOIN, чем смесь из LEFT JOIN и RIGHT JOIN.

Несколько LEFT JOIN подряд

В реальных запросах часто соединяют не две таблицы, а три, четыре и больше.

Например, хотим показать всех пользователей, их заказы и платежи по этим заказам.

SELECT
  u.name,
  o.id AS order_id,
  p.id AS payment_id,
  p.amount AS payment_amount
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
LEFT JOIN payments AS p
  ON o.id = p.order_id;

Здесь логика такая:

  1. Берём всех пользователей.
  2. Подставляем их заказы, если они есть.
  3. К заказам подставляем платежи, если они есть.

Но с цепочками LEFT JOIN нужно быть внимательным. Каждое следующее соединение работает уже не с «чистой» первой таблицей, а с результатом предыдущих соединений.

Если пользователь без заказов, то o.id будет NULL. Значит, платежи для него тоже не найдутся.

Это нормально, но на сложных запросах лучше проверять результат по шагам: сначала соединить две таблицы, посмотреть строки, потом добавить третью.

Дубликаты после LEFT JOIN

Иногда кажется, что LEFT JOIN «создал дубли». На самом деле он просто честно показал все совпадения справа.

Например, один пользователь сделал три заказа. Если соединить users с orders, пользователь появится три раза.

SELECT
  u.name,
  o.id AS order_id
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id;

Это не ошибка. Это естественный результат связи «один ко многим».

Если нужна одна строка на пользователя, нужно агрегировать:

SELECT
  u.name,
  COUNT(o.id) AS orders_count
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

LEFT JOIN отвечает за соединение строк. А за превращение многих строк в одну отвечает GROUP BY.

Как читать LEFT JOIN глазами

Возьмём запрос:

SELECT
  u.name,
  COUNT(o.id) AS orders_count
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

Читай его так:

«Возьми всех пользователей. Для каждого пользователя найди его заказы. Если заказов нет, пользователя всё равно оставь. Потом сгруппируй по пользователю и посчитай, сколько заказов нашлось».

А вот запрос:

SELECT
  u.name
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
WHERE o.id IS NULL;

Читай так:

«Возьми всех пользователей. Попробуй найти для них заказы. Оставь только тех, для кого заказов не нашлось».

Если ты можешь проговорить запрос обычными словами, значит, ты действительно понимаешь LEFT JOIN.

Частые ошибки с LEFT JOIN

Поставить фильтр по правой таблице в WHERE

Плохой вариант:

SELECT
  u.name,
  o.amount
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
WHERE o.amount > 1000;

Так пользователи без заказов исчезнут, потому что o.amount у них равен NULL.

Если нужно сохранить всех пользователей, а справа подставлять только дорогие заказы, условие нужно перенести в ON.

SELECT
  u.name,
  o.amount
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
 AND o.amount > 1000;

Использовать COUNT(*) вместо COUNT(o.id)

После LEFT JOIN такой запрос может обмануть:

SELECT
  u.name,
  COUNT(*) AS orders_count
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

Для пользователя без заказов COUNT(*) вернёт 1.

Правильнее считать реальную колонку заказа:

SELECT
  u.name,
  COUNT(o.id) AS orders_count
FROM users AS u
LEFT JOIN orders AS o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

Пугаться NULL

NULL после LEFT JOIN — это нормальный сигнал: справа нет пары.

Если нужно найти такие строки, используй:

WHERE o.id IS NULL

Если нужно красиво вывести ноль вместо пустоты, используй:

COALESCE(SUM(o.amount), 0)

Использовать LEFT JOIN, когда нужен INNER JOIN

Если нужны только строки с совпадениями, используй INNER JOIN.

LEFT JOIN нужен не «потому что он мощнее», а потому что задача требует сохранить левую таблицу полностью.

Не замечать размножение строк

Если справа несколько совпадений, строка слева появится несколько раз.

Это нормально для связей «один ко многим»: один пользователь — много заказов, один пост — много комментариев, один студент — много оценок.

Если нужна одна строка на объект, добавляй агрегаты и GROUP BY.

Главное про LEFT JOIN

LEFT JOIN сохраняет все строки из левой таблицы.

Если справа есть совпадение, SQL подставляет данные из правой таблицы.

Если совпадения нет, строка слева всё равно остаётся, а в колонках правой таблицы появляется NULL.

Базовая форма:

SELECT
  a.columns,
  b.columns
FROM table_a AS a
LEFT JOIN table_b AS b
  ON a.id = b.table_a_id;

LEFT JOIN особенно полезен, когда нужно увидеть полную картину: всех пользователей, все товары, все уроки, все записи — включая те, у которых пока нет связанных данных.

Чтобы найти строки без пары, используют паттерн:

SELECT
  a.columns
FROM table_a AS a
LEFT JOIN table_b AS b
  ON a.id = b.table_a_id
WHERE b.id IS NULL;

Самая частая ловушка: фильтр по правой таблице в WHERE может превратить LEFT JOIN почти в INNER JOIN. Если нужно сохранить строки слева, условия по правой таблице часто надо переносить в ON.

После LEFT JOIN для подсчёта совпадений обычно используют COUNT(b.id), а не COUNT(*).

Главная идея простая: LEFT JOIN — это способ сказать базе данных: «Покажи мне всё слева. А если справа что-то нашлось — добавь. Если не нашлось — не прячь строку, мне важно это увидеть».

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр