Все типы JOIN одной таблицей
Если нужен только ответ — вот он. «Левая» таблица та, что стоит до слова JOIN, «правая» — после.
| Тип | Что оставляет | Когда применять |
|---|---|---|
INNER JOIN |
Только строки, у которых есть пара в обеих таблицах | Нужны только совпадения. Например, заказы с известным пользователем |
LEFT JOIN |
Все строки левой таблицы; правая часть — NULL, если пары нет | Рабочая лошадка аналитика: сохранить всех пользователей, даже без заказов |
RIGHT JOIN |
То же зеркально: все строки правой таблицы | Почти не используют — переписывается в LEFT перестановкой таблиц |
FULL OUTER JOIN |
Все строки обеих таблиц, несовпавшие — с NULL | Сверка двух источников: что есть тут, чего нет там, и наоборот |
CROSS JOIN |
Каждая строка с каждой, без условия | Сетка комбинаций: все даты × все города. Или ошибка, если условие забыли |
JOIN не склеивает таблицы, а ищет пары
Главное недопонимание темы — представлять JOIN как приклеивание одной таблицы к другой сбоку. Из этой картинки следует ложный вывод, что строк в результате останется столько же, сколько было слева. На практике их может стать и меньше, и больше.
Точнее так: СУБД перебирает строки левой таблицы и для каждой ищет все подходящие строки правой — те, где выполняется условие после ON. Дальше начинается арифметика:
Тип JOIN отвечает ровно на один вопрос: что делать со строками, которым пары не нашлось. Совпавшие строки во всех типах ведут себя одинаково.
Визуализатор: посмотри, что происходит со строками
Две таблицы подобраны так, чтобы за один раз показать все случаи. У Ани два заказа — она даст две строки. У Веры и Глеба заказов нет — их видно только при LEFT и FULL. А заказ 104 оформлен на user_id = 5, которого нет в таблице пользователей, — он появится только при RIGHT и FULL.
| id | name |
|---|
| id | user_id | sum |
|---|
| u.id | u.name | o.id | o.sum |
|---|
Каждый тип по отдельности
INNER JOIN — только совпадения
Оставляет строки, для которых пара нашлась с обеих сторон. Всё остальное отбрасывается молча — и это его главная опасность.
SELECT u.name, o.id, o.sum FROM users u INNER JOIN orders o ON u.id = o.user_id; -- 3 строки: Аня×2, Борис×1 -- Вера и Глеб исчезли: у них нет заказов
LEFT JOIN — сохранить всех слева
Самый частый JOIN в аналитике. Берёт все строки левой таблицы, а тем, кому пары не нашлось, подставляет NULL в колонки правой.
SELECT u.name, o.id, o.sum FROM users u LEFT JOIN orders o ON u.id = o.user_id; -- 5 строк: Аня×2, Борис×1, Вера→NULL, Глеб→NULL
Отсюда же берётся приём «найти тех, у кого ничего нет» — антиджойн. Делаем LEFT JOIN и оставляем строки, где правая часть оказалась пустой:
SELECT u.name FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL; -- Вера, Глеб
RIGHT JOIN — то же самое наоборот
Зеркало LEFT: сохраняет все строки правой таблицы. Новых возможностей не даёт — любой RIGHT JOIN превращается в LEFT перестановкой таблиц местами. На практике его почти не пишут: запрос читается легче, когда главная таблица стоит первой и все соединения идут в одну сторону. Знать стоит, чтобы разбирать чужой код и не растеряться на собеседовании.
FULL OUTER JOIN — всё со всех сторон
Оставляет все строки обеих таблиц. Рабочий сценарий — сверка двух источников, когда важны расхождения в обе стороны: что есть в CRM и нет в биллинге, и наоборот.
CROSS JOIN — каждая с каждой
Соединяет без условия: каждая строка левой таблицы с каждой строкой правой. На четырёх и четырёх строках получается 16 — переключи визуализатор и посмотри.
Осмысленное применение одно: построить полную сетку комбинаций. Например, все даты месяца × все города, чтобы потом подтянуть к ней продажи и увидеть дни с нулём вместо пропущенных строк.
Почему круги Эйлера врут
Диаграммы с двумя пересекающимися кругами — самая тиражируемая картинка про JOIN. Она удобна и в одном месте прямо ошибочна.
Круги описывают операции над множествами: элемент либо есть, либо нет. JOIN работает не с множествами, а с таблицами, где строки могут повторяться. И вся разница видна на нашем примере: пользователей 4, а LEFT JOIN даёт 5 строк.
Практический вывод: перед JOIN проверяй, уникален ли ключ в правой таблице. Если да — число строк не вырастет, и круги как грубая аналогия сойдут. Если нет — считай, сколько пар придётся на строку, иначе получишь задвоенные суммы.
SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING COUNT(*) > 1; -- вернулись строки → ключ не уникален → JOIN размножит строки
5 ошибок, которые ломают цифры в отчётах
1. Условие на правую таблицу в WHERE вместо ON
Самая коварная. ON работает до соединения и решает, что считать парой. WHERE работает после и фильтрует готовый результат — вместе со строками, где правая часть равна NULL. LEFT JOIN молча становится INNER.
-- ❌ Вера и Глеб исчезнут: у них o.sum = NULL, а NULL > 500 → не проходит FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.sum > 500; -- ✅ Все пользователи на месте, но подтянутся только заказы дороже 500 FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.sum > 500;
Проверь себя
Все задачи — на тех же двух таблицах из визуализатора. Сначала ответь, потом сверься.
Показать ответ
Показать ответ
Показать ответ
Показать ответ
Частые вопросы про JOIN
Связанные материалы
Главное про JOIN
JOIN не приклеивает таблицы, а ищет пары строк по условию. Тип JOIN отвечает только на один вопрос: что делать с теми, кому пары не нашлось. Совпавшие строки во всех типах ведут себя одинаково.
LEFT JOIN — рабочая лошадка аналитика: сохраняет всех слева и не теряет данные молча. INNER удобен, но выкидывает несовпавшее без предупреждения. FULL нужен для сверок, CROSS — для сеток комбинаций, RIGHT можно не писать никогда.
Практика: возьми любой свой запрос с JOIN и посчитай COUNT(*) до и после соединения. Если число выросло — ключ не уникален, и все суммы в этом отчёте нужно перепроверить.