LEFT JOIN (или полное название LEFT OUTER JOIN) — это соединение, при котором все строки левой таблицы попадают в результат. Если в правой таблице нет совпадения, на её колонках будет NULL.
Контрастно с INNER JOIN: тот выкидывает строки без пары. LEFT JOIN их сохраняет, просто помечает «справа ничего».
Аналогия. Учитель раздаёт ученикам тесты на проверку. Не все сдали.
INNER JOIN — «покажи мне только тех, кто сдал тест и оценку».
LEFT JOIN — «покажи мне всех учеников, и если сдали — оценку, если нет — пустое место».
Второй вариант чаще нужен в реальной жизни — нужно ВИДЕТЬ, кто отстал.
Зачем нужен LEFT JOIN
Классические сценарии:
- Список с дополнительной информацией, даже если её нет. Например, все пользователи + сколько у них заказов (включая 0).
- Найти «сирот» — записи без пары. Пользователи, у которых нет ни одного заказа. Товары, которых ни разу не покупали. И так далее.
- Подсчёт «дыр» в данных. Сколько постов без комментариев, сколько уроков без домашек.
INNER JOIN для этих задач не подходит — он сразу выбрасывает «бездетные» строки.
Базовый синтаксис
SELECT колонки
FROM таблица_A
LEFT JOIN таблица_B
ON A.ключ = B.ключ;
«Левая» — это та, что после FROM. «Правая» — после LEFT JOIN. Запомни направление: LEFT — значит, левая полностью сохраняется.
OUTER можно опускать. LEFT JOIN и LEFT OUTER JOIN — это одно и то же.
Пример: школа
students:
| id |
name |
| 1 |
Анна |
| 2 |
Борис |
| 3 |
Вера |
| 4 |
Григорий |
grades (оценки за тест):
| id |
student_id |
score |
| 10 |
1 |
5 |
| 11 |
1 |
4 |
| 12 |
2 |
3 |
Запрос: «все ученики и их оценки, если есть».
SELECT s.name, g.score
FROM students s
LEFT JOIN grades g ON s.id = g.student_id;
Результат:
| name |
score |
| Анна |
5 |
| Анна |
4 |
| Борис |
3 |
| Вера |
NULL |
| Григорий |
NULL |
Что видно:
- Все 4 ученика в результате. Это и есть смысл LEFT JOIN.
- У Анны две оценки — две строки.
- У Бориса одна — одна строка.
- У Веры и Григория нет оценок → одна строка с
NULL в score.
Если бы мы написали INNER JOIN, в результате остались бы только Анна (2 строки) и Борис. Вера и Григорий выпали бы — а ведь именно они интересны учителю.
Поиск «сирот» — записей без пары
Классический трюк. Чтобы найти учеников, которые не написали ни одного теста, фильтруй на IS NULL:
SELECT s.name
FROM students s
LEFT JOIN grades g ON s.id = g.student_id
WHERE g.id IS NULL;
Результат:
Логика: LEFT JOIN сохранил всех учеников. У тех, у кого нет оценок, в g.id стоит NULL. Фильтр WHERE g.id IS NULL оставляет именно их.
Этот паттерн (LEFT JOIN ... WHERE other.id IS NULL) — самый частый способ найти «нет совпадения». Используется везде: пользователи без заказов, посты без комментариев, товары, которые никто не купил.
Не используй для фильтра колонку правой таблицы
Ловушка. Если хочешь оставить только тех, кто получил 5, и пишешь:
SELECT s.name, g.score
FROM students s
LEFT JOIN grades g ON s.id = g.student_id
WHERE g.score = 5;
Результат:
Вера и Григорий выпали. Потому что условие WHERE g.score = 5 для них даёт NULL = 5 (это не TRUE), и они отфильтровались. По сути LEFT JOIN превратился в INNER JOIN.
Если хочешь оставить всех учеников, но только пятёрки в правой части — условие надо ставить в ON, а не в WHERE:
SELECT s.name, g.score
FROM students s
LEFT JOIN grades g ON s.id = g.student_id AND g.score = 5;
Результат:
| name |
score |
| Анна |
5 |
| Борис |
NULL |
| Вера |
NULL |
| Григорий |
NULL |
Теперь все четверо в результате; оценка показана только если она 5. Различие тонкое, но критичное. Запомни: условия по правой таблице — в ON, по левой — в WHERE.
INNER JOIN vs LEFT JOIN — наглядная разница
Представь две команды футболистов: players (игроки) и goals (забитые голы за матч). Не каждый игрок забил.
INNER JOIN → только игроки, которые забили хотя бы один гол.
LEFT JOIN от players к goals → все игроки, у тех, кто не забил, в колонке гола стоит NULL.
LEFT JOIN + WHERE goals.id IS NULL → только игроки, которые не забили ни одного гола.
Три разных вопроса — три разных запроса.
Большой пример: e-commerce
users:
| id |
name |
signup_date |
| 1 |
Анна |
2024-01-15 |
| 2 |
Борис |
2024-02-01 |
| 3 |
Вера |
2024-02-20 |
| 4 |
Григорий |
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 |
Вопрос бизнеса: «сколько заказов и общая сумма у каждого зарегистрированного пользователя? Включая тех, кто пока ничего не купил».
SELECT
u.name,
COUNT(o.id) AS orders_count,
COALESCE(SUM(o.amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_spent DESC;
Результат:
| name |
orders_count |
total_spent |
| Анна |
2 |
2000 |
| Борис |
1 |
200 |
| Вера |
0 |
0 |
| Григорий |
0 |
0 |
Важные нюансы:
- Все 4 пользователя в результате, даже Вера и Григорий, у которых нет заказов.
COUNT(o.id) для них даёт 0 (NULL не считается в COUNT).
SUM для них вернул бы NULL, поэтому обернули в COALESCE(..., 0) — заменяет NULL на 0 для красивого вывода.
Это типовой запрос для дашбордов: «активность каждого пользователя, включая нулевую».
Частые ошибки новичков
1. WHERE на правой таблице, который превращает LEFT в INNER. Самый частый и самый коварный косяк. Если фильтр опирается на колонку правой части — клади его в ON. Если на левую — в WHERE.
2. Не понимать NULL в результате. Видят NULL и пугаются. На самом деле это нормальный сигнал «справа нет пары». Используй IS NULL, COALESCE, агрегаты, которые умеют игнорировать NULL — и всё ок.
3. Использовать LEFT JOIN, когда нужен INNER. Если бизнес-задача «только пользователи, у которых ЕСТЬ заказы» — INNER JOIN. LEFT JOIN тут даст лишние строки и потенциально замедлит запрос.
4. Несколько LEFT JOIN-ов и логика теряется. Когда соединяешь A LEFT JOIN B LEFT JOIN C, помни: каждое следующее условие — относительно того, что получилось до. На сложных JOIN-цепочках лучше пошагово отлаживать.
5. RIGHT JOIN вместо переворота. RIGHT JOIN существует, делает то же самое, но с противоположной стороны. На практике почти не используется — проще поменять таблицы местами и оставить LEFT JOIN. Так читать понятнее.
6. COUNT(*) вместо COUNT(колонка) после LEFT JOIN. COUNT(*) считает все строки, включая те, где правая часть NULL. COUNT(o.id) считает только реальные совпадения. На LEFT JOIN это даёт разный результат.
Мини-резюме
LEFT JOIN сохраняет ВСЕ строки левой таблицы.
- Если в правой нет пары — на её колонках стоит
NULL.
LEFT JOIN ... WHERE other.id IS NULL — стандартный способ найти строки без пары.
- Условия по правой таблице клади в
ON, по левой — в WHERE.
OUTER опционален; LEFT JOIN = LEFT OUTER JOIN.
- Чаще нужен в реальных задачах, чем INNER, — потому что обычно мы хотим видеть полную картину, включая «нули».
LEFT JOIN(или полное названиеLEFT OUTER JOIN) — это соединение, при котором все строки левой таблицы попадают в результат. Если в правой таблице нет совпадения, на её колонках будетNULL.Контрастно с
INNER JOIN: тот выкидывает строки без пары.LEFT JOINих сохраняет, просто помечает «справа ничего».Аналогия. Учитель раздаёт ученикам тесты на проверку. Не все сдали.
INNER JOIN— «покажи мне только тех, кто сдал тест и оценку».LEFT JOIN— «покажи мне всех учеников, и если сдали — оценку, если нет — пустое место».Второй вариант чаще нужен в реальной жизни — нужно ВИДЕТЬ, кто отстал.
Зачем нужен LEFT JOIN
Классические сценарии:
INNER JOINдля этих задач не подходит — он сразу выбрасывает «бездетные» строки.Базовый синтаксис
SELECT колонки FROM таблица_A -- левая LEFT JOIN таблица_B -- правая ON A.ключ = B.ключ;«Левая» — это та, что после
FROM. «Правая» — послеLEFT JOIN. Запомни направление: LEFT — значит, левая полностью сохраняется.OUTERможно опускать.LEFT JOINиLEFT OUTER JOIN— это одно и то же.Пример: школа
students:grades(оценки за тест):Запрос: «все ученики и их оценки, если есть».
SELECT s.name, g.score FROM students s LEFT JOIN grades g ON s.id = g.student_id;Результат:
Что видно:
NULLвscore.Если бы мы написали
INNER JOIN, в результате остались бы только Анна (2 строки) и Борис. Вера и Григорий выпали бы — а ведь именно они интересны учителю.Поиск «сирот» — записей без пары
Классический трюк. Чтобы найти учеников, которые не написали ни одного теста, фильтруй на
IS NULL:SELECT s.name FROM students s LEFT JOIN grades g ON s.id = g.student_id WHERE g.id IS NULL;Результат:
Логика:
LEFT JOINсохранил всех учеников. У тех, у кого нет оценок, вg.idстоитNULL. ФильтрWHERE g.id IS NULLоставляет именно их.Этот паттерн (
LEFT JOIN ... WHERE other.id IS NULL) — самый частый способ найти «нет совпадения». Используется везде: пользователи без заказов, посты без комментариев, товары, которые никто не купил.Не используй для фильтра колонку правой таблицы
Ловушка. Если хочешь оставить только тех, кто получил 5, и пишешь:
SELECT s.name, g.score FROM students s LEFT JOIN grades g ON s.id = g.student_id WHERE g.score = 5;Результат:
Вера и Григорий выпали. Потому что условие
WHERE g.score = 5для них даётNULL = 5(это не TRUE), и они отфильтровались. По сути LEFT JOIN превратился в INNER JOIN.Если хочешь оставить всех учеников, но только пятёрки в правой части — условие надо ставить в ON, а не в WHERE:
SELECT s.name, g.score FROM students s LEFT JOIN grades g ON s.id = g.student_id AND g.score = 5;Результат:
Теперь все четверо в результате; оценка показана только если она 5. Различие тонкое, но критичное. Запомни: условия по правой таблице — в
ON, по левой — вWHERE.INNER JOIN vs LEFT JOIN — наглядная разница
Представь две команды футболистов:
players(игроки) иgoals(забитые голы за матч). Не каждый игрок забил.INNER JOIN→ только игроки, которые забили хотя бы один гол.LEFT JOINотplayersкgoals→ все игроки, у тех, кто не забил, в колонке гола стоитNULL.LEFT JOIN+WHERE goals.id IS NULL→ только игроки, которые не забили ни одного гола.Три разных вопроса — три разных запроса.
Большой пример: e-commerce
users:orders:Вопрос бизнеса: «сколько заказов и общая сумма у каждого зарегистрированного пользователя? Включая тех, кто пока ничего не купил».
SELECT u.name, COUNT(o.id) AS orders_count, COALESCE(SUM(o.amount), 0) AS total_spent FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name ORDER BY total_spent DESC;Результат:
Важные нюансы:
COUNT(o.id)для них даёт 0 (NULL не считается в COUNT).SUMдля них вернул быNULL, поэтому обернули вCOALESCE(..., 0)— заменяет NULL на 0 для красивого вывода.Это типовой запрос для дашбордов: «активность каждого пользователя, включая нулевую».
Частые ошибки новичков
1. WHERE на правой таблице, который превращает LEFT в INNER. Самый частый и самый коварный косяк. Если фильтр опирается на колонку правой части — клади его в
ON. Если на левую — вWHERE.2. Не понимать NULL в результате. Видят NULL и пугаются. На самом деле это нормальный сигнал «справа нет пары». Используй
IS NULL,COALESCE, агрегаты, которые умеют игнорировать NULL — и всё ок.3. Использовать LEFT JOIN, когда нужен INNER. Если бизнес-задача «только пользователи, у которых ЕСТЬ заказы» —
INNER JOIN.LEFT JOINтут даст лишние строки и потенциально замедлит запрос.4. Несколько LEFT JOIN-ов и логика теряется. Когда соединяешь A LEFT JOIN B LEFT JOIN C, помни: каждое следующее условие — относительно того, что получилось до. На сложных JOIN-цепочках лучше пошагово отлаживать.
5. RIGHT JOIN вместо переворота.
RIGHT JOINсуществует, делает то же самое, но с противоположной стороны. На практике почти не используется — проще поменять таблицы местами и оставитьLEFT JOIN. Так читать понятнее.6. COUNT(*) вместо COUNT(колонка) после LEFT JOIN.
COUNT(*)считает все строки, включая те, где правая часть NULL.COUNT(o.id)считает только реальные совпадения. На LEFT JOIN это даёт разный результат.Мини-резюме
LEFT JOINсохраняет ВСЕ строки левой таблицы.NULL.LEFT JOIN ... WHERE other.id IS NULL— стандартный способ найти строки без пары.ON, по левой — вWHERE.OUTERопционален;LEFT JOIN=LEFT OUTER JOIN.