Это часть 5 из 10 учебника «SQL с нуля для аналитика». Содержание серии в конце поста. ← Часть 4
TL;DR: JOIN соединяет строки из двух таблиц по условию. INNER JOIN — только пересечение (строки есть в обеих). LEFT JOIN — все из левой + совпадения из правой. RIGHT JOIN — зеркало LEFT. FULL OUTER JOIN — все из обеих. CROSS JOIN — декартово произведение (каждая со каждой). 80% работы — INNER и LEFT.
В этой части:
- Зачем нужен JOIN (реальный сценарий)
- INNER JOIN с примером
- LEFT JOIN — самый частый у аналитика
- RIGHT и FULL OUTER — когда нужны
- CROSS JOIN — редкий, но мощный
- Fan-out проблема и как её ловить
Зачем аналитику 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;
Результат:
| country | amount | |
|---|---|---|
| ivan | RU | 500 |
| ivan | RU | 300 |
| maria | KZ | 700 |
Юзер 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;
Результат:
| country | amount | |
|---|---|---|
| ivan | RU | 500 |
| ivan | RU | 300 |
| maria | KZ | 700 |
| john | US | NULL |
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?
- Ошибка 1: Забыли
ON→ CROSS JOIN вместо INNER. Миллион строк × миллион = катастрофа. - Ошибка 2: Опечатка в
ON:ON u.id = o.user_idнаписали какON u.id = o.order_id— joinим не по тому ключу. - Ошибка 3: Fan-out не учли — метрики раздуты.
- Ошибка 4: LEFT JOIN + WHERE на правой таблице.
WHERE o.amount > 0после LEFT JOIN превращает его в INNER (NULL не пройдёт фильтр). - Ошибка 5: JOIN без алиасов → читать невозможно. Всегда
users u JOIN orders o.
Подробнее в 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 — все типы с примерами для углубления.
Источники
- PostgreSQL Docs: «Joined Tables» (postgresql.org/docs/current/queries-table-expressions.html)
- SQLBolt: «SQL Joins» (sqlbolt.com)