JOIN в SQL — полный гайд по соединению таблиц
Коротко
JOIN — оператор SQL, который соединяет строки из двух (или более) таблиц по условию. В реальных базах данные разложены по разным таблицам, и без JOIN вы не напишете ни одного полезного запроса. На собеседованиях аналитика данных JOIN спрашивают почти всегда.
В SQL пять основных типов JOIN: INNER, LEFT, RIGHT, FULL и CROSS. Разберём каждый, покажем диаграммы, примеры и типичные ошибки.
Данные для примеров
Две таблицы — users и orders:
users:
| user_id | name |
|---|---|
| 1 | Анна |
| 2 | Борис |
| 3 | Вика |
| 4 | Глеб |
orders:
| order_id | user_id | amount |
|---|---|---|
| 101 | 1 | 500 |
| 102 | 1 | 300 |
| 103 | 3 | 700 |
| 104 | 5 | 200 |
Обратите внимание: у Бориса и Глеба нет заказов, а заказ 104 ссылается на user_id = 5, которого нет в users.
INNER JOIN
Возвращает только строки, у которых есть совпадение в обеих таблицах. Нет пары — строка отбрасывается.
A B
┌───┐ ┌───┐
│ │▓▓▓│ │ ▓ = результат
│ │▓▓▓│ │
└───┘ └───┘SELECT u.user_id, u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;Результат: 3 строки — Анна (2 заказа) и Вика (1 заказ). Борис, Глеб и заказ 104 не попали.
LEFT JOIN
Сохраняет все строки из левой таблицы. Если совпадения в правой нет — колонки правой заполняются NULL.
A B
┌───┐ ┌───┐
│▓▓▓│▓▓▓│ │ ▓ = результат
│▓▓▓│▓▓▓│ │ вся левая + пересечение
└───┘ └───┘SELECT u.user_id, u.name, o.order_id
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;Результат: все 4 пользователя. У Бориса и Глеба order_id = NULL. Заказ 104 (user_id = 5) не попал — его нет в левой таблице.
LEFT JOIN — самый используемый тип в аналитике. Подробнее о разнице с INNER: LEFT JOIN vs INNER JOIN.
RIGHT JOIN
Зеркало LEFT JOIN: сохраняет все строки из правой таблицы.
A B
┌───┐ ┌───┐
│ │▓▓▓│▓▓▓│ ▓ = результат
│ │▓▓▓│▓▓▓│ пересечение + вся правая
└───┘ └───┘SELECT u.name, o.order_id, o.amount
FROM users u
RIGHT JOIN orders o ON u.user_id = o.user_id;Результат: все 4 заказа. У заказа 104 name = NULL, потому что пользователя 5 нет в users.
На практике RIGHT JOIN почти не используют — проще поменять таблицы местами и написать LEFT JOIN. Подробнее: LEFT JOIN vs RIGHT JOIN.
FULL OUTER JOIN
Сохраняет все строки из обеих таблиц. Где нет пары — NULL.
A B
┌───┐ ┌───┐
│▓▓▓│▓▓▓│▓▓▓│ ▓ = результат
│▓▓▓│▓▓▓│▓▓▓│ всё из обеих таблиц
└───┘ └───┘SELECT u.name, o.order_id
FROM users u
FULL OUTER JOIN orders o ON u.user_id = o.user_id;Результат: и пользователи без заказов (Борис, Глеб), и заказы без пользователей (104). Полезен для поиска рассинхронов между таблицами.
CROSS JOIN
Декартово произведение: каждая строка одной таблицы соединяется с каждой строкой другой. Условие ON не нужно. M строк × N строк = M×N строк.
SELECT u.name, o.order_id
FROM users u
CROSS JOIN orders o;4 пользователя × 4 заказа = 16 строк. Применяется для генерации date spine, gap-анализа и тестовых данных. Подробнее: CROSS JOIN и SELF JOIN.
Условие ON — как работает соединение
Условие после ON определяет, по какому ключу соединяются строки. Чаще всего это равенство первичного и внешнего ключа:
ON u.user_id = o.user_idМожно добавлять дополнительные условия:
LEFT JOIN orders o
ON u.user_id = o.user_id
AND o.created_at >= '2026-01-01'Это важно для LEFT JOIN: условие в ON фильтрует только правую таблицу, сохраняя все строки левой. Если перенести условие в WHERE — LEFT JOIN превратится в INNER JOIN (об этом ниже).
Несколько JOIN в одном запросе
В реальных запросах таблиц обычно больше двух. JOIN-ы пишутся последовательно:
SELECT
u.name,
o.order_id,
p.product_name,
c.city
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
LEFT JOIN products p ON o.product_id = p.product_id
LEFT JOIN cities c ON u.city_id = c.city_id;Каждый следующий JOIN работает с результатом предыдущего. Порядок имеет значение: если первый JOIN отфильтровал строки (INNER), следующие не вернут их обратно.
JOIN с агрегацией
Типичная задача аналитика — посчитать метрики по пользователям:
SELECT
u.user_id,
u.name,
COUNT(o.order_id) AS orders_count,
COALESCE(SUM(o.amount), 0) AS total_amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.name;LEFT JOIN + GROUP BY покажет всех пользователей, включая тех, у кого 0 заказов. INNER JOIN потерял бы их.
Хотите попрактиковать такие запросы? Попробуйте SQL-тренажёр или откройте тренажёр прямо в Telegram.
Anti-join: LEFT JOIN + IS NULL
Паттерн для поиска записей без совпадений — один из самых частых вопросов на собеседованиях:
-- Пользователи, которые ни разу не купили
SELECT u.user_id, u.name
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.order_id IS NULL;Результат: Борис и Глеб. Альтернативы — NOT EXISTS и NOT IN, но LEFT JOIN + IS NULL — самый читаемый и часто самый быстрый вариант.
Self join
Соединение таблицы с самой собой через алиасы. Классика — найти пары, иерархии или последовательные события:
-- Сотрудник и его руководитель
SELECT
e.name AS employee,
m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;Подробнее с примерами: CROSS JOIN и SELF JOIN в SQL.
Типичные ошибки
1. Фильтрация в WHERE вместо ON для LEFT JOIN
-- Ошибка: превращает LEFT JOIN в INNER JOIN
SELECT u.*, o.order_id
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.status = 'paid';
-- Правильно: условие в ON
SELECT u.*, o.order_id
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
AND o.status = 'paid';WHERE отсекает строки с NULL в o.status — а это все пользователи без заказов. LEFT JOIN теряет смысл.
2. Неожиданное дублирование строк
Если в правой таблице несколько совпадений по ключу, JOIN создаст по строке на каждое. Пользователь с 10 заказами появится 10 раз. Если вам нужна одна строка — используйте агрегацию или подзапрос.
3. JOIN без индекса
JOIN по колонке без индекса на больших таблицах работает медленно. Убедитесь, что колонки в ON проиндексированы — это первое, что стоит проверить при тормозящих запросах.
4. CROSS JOIN по ошибке
Забыли условие ON? Получили декартово произведение. 10 000 × 10 000 = 100 миллионов строк. Всегда проверяйте количество строк после JOIN.
Вопросы с собеседований
- «Перечислите типы JOIN.» — INNER, LEFT, RIGHT, FULL OUTER, CROSS. Иногда добавляют SELF JOIN, хотя технически это не отдельный тип, а способ использования.
- «Чем LEFT JOIN отличается от INNER JOIN?» — LEFT сохраняет все строки левой таблицы, INNER — только совпадения. Подробный разбор.
- «Как найти пользователей без заказов?» — LEFT JOIN users с orders + WHERE orders.id IS NULL. Это anti-join паттерн.
- «Что будет, если в LEFT JOIN добавить условие на правую таблицу в WHERE?» — LEFT JOIN превратится в INNER JOIN, потому что WHERE отфильтрует NULL-строки.
- «Может ли JOIN увеличить количество строк?» — Да, если в правой таблице несколько совпадений по ключу. Это частая причина ошибок в метриках.
- «Когда используют CROSS JOIN?» — Для генерации всех комбинаций: date spine, gap-анализ, тестовые данные.
FAQ
Какой JOIN используется по умолчанию?
Если написать просто JOIN без указания типа — это INNER JOIN. Но лучше всегда писать явно для читаемости.
Можно ли соединять больше двух таблиц?
Да, количество JOIN-ов не ограничено. В аналитических запросах 3-5 JOIN-ов — норма. Главное — следить за тем, как каждый JOIN влияет на количество строк.
В чём разница между ON и WHERE?
Для INNER JOIN — разницы нет. Для LEFT JOIN — критическая: условие в ON фильтрует правую таблицу до соединения, условие в WHERE — после, отсекая строки с NULL.
Какой JOIN самый быстрый?
INNER JOIN обычно чуть быстрее LEFT JOIN, потому что возвращает меньше строк. Но разница на проиндексированных колонках минимальна. Выбирайте JOIN по смыслу задачи, а не по скорости.
Хотите закрепить JOIN-ы на практике? В тренажёре Карьерник — сотни вопросов по SQL, включая JOIN, подзапросы и оконные функции. А если нужна компактная шпаргалка — загляните в шпаргалку по JOIN.
Ещё больше примеров вопросов — на странице примеры вопросов.