SQLучебникдля начинающихJOINчасть-5

SQL с нуля. Часть 5: JOIN — INNER, LEFT, RIGHT, FULL, CROSS

2026-06-02 9 мин

Это часть 5 из 10 учебника «SQL с нуля для аналитика». Содержание серии в конце поста. ← Часть 4


TL;DR: JOIN соединяет строки из двух таблиц по условию. INNER JOIN — только пересечение (строки есть в обеих). LEFT JOIN — все из левой + совпадения из правой. RIGHT JOIN — зеркало LEFT. FULL OUTER JOIN — все из обеих. CROSS JOIN — декартово произведение (каждая со каждой). 80% работы — INNER и LEFT.

В этой части:


Зачем аналитику JOIN?

Данные о пользователе хранятся в users. Данные о заказах — в orders. Чтобы посчитать «выручка по странам», надо соединить orders с users (orders.user_id → users.id).

Таблица users:               Таблица orders:
  id | email | country         order_id | user_id | amount
  1  | ivan  | RU              101      | 1       | 500
  2  | maria | KZ              102      | 1       | 300
  3  | john  | US              103      | 2       | 700

Без JOIN мы видим только id юзера в orders. Чтобы получить страну — JOIN.

Что такое INNER JOIN?

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

!INNER JOIN: диаграмма Венна — только пересечение A и B

SELECT
    u.email,
    u.country,
    o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

Результат:

emailcountryamount
ivanRU500
ivanRU300
mariaKZ700

Юзер john (id=3) не попал — у него нет заказов.

INNER — ключевое слово, можно опустить: JOIN orders o ON ... = INNER JOIN.

Что такое LEFT JOIN и когда он нужен?

LEFT JOIN — все строки из левой таблицы + совпадения из правой. Если совпадения нет — NULL.

!LEFT JOIN: все строки из A (даже без пары в B)

SELECT
    u.email,
    u.country,
    o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

Результат:

emailcountryamount
ivanRU500
ivanRU300
mariaKZ700
johnUSNULL

John попал, но amount = NULL — у него заказов нет.

Самый частый случай: «все пользователи + сколько у них заказов (включая 0)».

SELECT
    u.email,
    COUNT(o.order_id) AS orders_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.email;
Типичный кейс: «все юзеры + количество их заказов (включая 0)». users LEFT JOIN orders ON ... GROUP BY user_id — даёт включая тех, кто ни разу не покупал. Полезно для cohort retention.

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

RIGHT JOIN — зеркало LEFT. Все строки из правой таблицы + совпадения слева.

-- Эквивалентные запросы:
SELECT * FROM A LEFT JOIN B ON A.id = B.a_id;
SELECT * FROM B RIGHT JOIN A ON A.id = B.a_id;

В реальной работе RIGHT JOIN почти не используется — все пишут LEFT, просто меняя порядок таблиц. Знать нужно для код-ревью legacy запросов.

Что такое FULL OUTER JOIN?

FULL OUTER JOIN — все строки из обеих таблиц. Если пары нет — NULL с той стороны.

!FULL OUTER JOIN: всё из A + всё из B

Как БД ищет совпадения строк — пошаговая анимация:

!JOIN execution: матчинг строк по очереди

SELECT *
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

Полезно для reconciliation: «найди записи, которые есть в одной таблице, но нет в другой».

-- Юзеры без заказов И заказы без юзеров (orphans)
SELECT *
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id
WHERE u.id IS NULL OR o.order_id IS NULL;

Когда нужен CROSS JOIN?

CROSS JOIN — декартово произведение. Каждая строка А с каждой строкой Б.

-- Все комбинации стран и продуктов
SELECT c.country, p.product
FROM countries c
CROSS JOIN products p;
-- Если 50 стран и 100 продуктов → 5000 строк

Используется редко. Самый частый легитимный случай — генерация календаря:

SELECT u.user_id, d.day
FROM users u
CROSS JOIN generate_series(
    '2026-01-01'::date,
    '2026-12-31'::date,
    INTERVAL '1 day'
) AS d(day);
-- Для каждого юзера — каждая дата (для retention анализа)

Что такое fan-out и как его ловить?

Fan-out — когда JOIN множит строки больше чем ожидалось. Типичная причина: правая таблица имеет несколько совпадений на одну строку левой.

users (1 строка ivan):
  id=1, email=ivan

orders (3 заказа ivan):
  order_id=101, user_id=1, amount=500
  order_id=102, user_id=1, amount=300
  order_id=103, user_id=1, amount=200

INNER JOIN → 3 строки с email=ivan

SUM(amount) → 1000 (правильно)
SUM(some_user_metric) → утроится (НЕправильно)

Симптом: метрики кажутся в 2-10× больше ожидаемых.

Решение: либо агрегировать одну сторону до JOIN, либо явно SELECT DISTINCT, либо считать через подзапрос.

-- Безопасно: агрегируем заказы до JOIN
SELECT u.email, o.total
FROM users u
LEFT JOIN (
    SELECT user_id, SUM(amount) AS total
    FROM orders
    GROUP BY user_id
) o ON u.id = o.user_id;

(Подзапросы — в Части 6).

Типичный случай fan-out: JOIN orders с order_items (3 позиции в среднем на заказ) даёт SUM(order.total) в 3 раза больше, потому что каждая строка orders дублируется 3 раза. Симптом: «выручка в дашборде = 12М, в БД 4М, потому что забыли DISTINCT». Фикс: агрегируй order_items до JOIN.

Какие 5 типичных ошибок JOIN?

Подробнее в SQL антипаттернах.

Частые вопросы про JOIN

Сколько таблиц можно JOIN в одном запросе?

Сколько угодно. Но 10+ — признак того, что нужна CTE или модель данных. Подробнее в Части 6.

Чем JOIN отличается от UNION?

JOIN соединяет колонки (горизонтально). UNION соединяет строки (вертикально). UNION ALL — без удаления дублей, быстрее.

Можно ли JOIN по нескольким колонкам?

ON A.x = B.x AND A.y = B.y — да. Часто для composite key.

Что делать если ключи в разных типах?

Использовать CAST: ON CAST(A.id AS BIGINT) = B.id. Но лучше — починить data модель.

NATURAL JOIN — что это?

JOIN по всем одинаковым именам колонок. Не используй в production — неявные правила = непонятный код.

Что дальше?

В Части 6 — подзапросы и CTE (WITH). Без них сложные запросы превращаются в нечитаемое месиво.

Сейчас открой SQL-тренажёр и попробуй задачу с LEFT JOIN.

В Pro — безлимит мок-собесов на AI-интервью + 491 SQL-задача + 612 тестовых заданий + 50+ блог-постов.


Навигация по учебнику

← Часть 4 | Часть 5: JOIN | Часть 6 →

Содержание серии: 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10

← Вернуться к оглавлению

См. также JOIN — все типы с примерами для углубления.

Источники

SQL-тренажёр
Практика JOIN на реальных задачах. 491 SQL-задача, первые 5 бесплатно.
Открыть тренажёр →