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.

Ещё больше примеров вопросов — на странице примеры вопросов.