JOIN — один из ключевых навыков в SQL. С ним данные из нескольких таблиц объединяются в один результат. Разберём с нуля.
Зачем нужен JOIN
Данные в реляционной базе разбиты по разным таблицам. Пользователи — в users, заказы — в orders. Чтобы получить «заказы с именами пользователей» — нужен JOIN.
users orders
┌────┬──────┐ ┌────┬─────────┬────────┐
│ id │ name │ │ id │ user_id │ amount │
├────┼──────┤ ├────┼─────────┼────────┤
│ 1 │ Анна │ │101 │ 1 │ 1500 │
│ 2 │ Иван │ │102 │ 1 │ 2300 │
│ 3 │ Маша │ │103 │ 2 │ 890 │
└────┴──────┘ └────┴─────────┴────────┘
Хотим: имя пользователя + сумма каждого заказа.
INNER JOIN — только совпадения
SELECT users.name, orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;
Результат:
| name | amount |
|---|---|
| Анна | 1500 |
| Анна | 2300 |
| Иван | 890 |
Маша (id=3) не попала — у неё нет заказов. INNER JOIN возвращает только строки где есть совпадение в обеих таблицах.
LEFT JOIN — все строки из левой таблицы
SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
Результат:
| name | amount |
|---|---|
| Анна | 1500 |
| Анна | 2300 |
| Иван | 890 |
| Маша | NULL |
Маша есть в результате, но amount = NULL (нет заказов). LEFT JOIN возвращает все строки из левой таблицы (users), а для несовпадений — NULL из правой.
RIGHT JOIN — все строки из правой таблицы
Зеркало LEFT JOIN. Редко используется — обычно переписывают как LEFT JOIN меняя таблицы местами.
FULL OUTER JOIN — все строки из обеих таблиц
SELECT users.name, orders.amount
FROM users
FULL OUTER JOIN orders ON users.id = orders.user_id;
Все пользователи + все заказы. Несовпадения с обеих сторон заполняются NULL.
Псевдонимы таблиц
Для удобства таблицам дают короткие псевдонимы:
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
Несколько JOIN
-- Заказ → пользователь → товар
SELECT u.name, p.name AS product, oi.quantity
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id;
Каждый JOIN добавляет одну таблицу к результату.
JOIN + GROUP BY
Частый паттерн:
-- Количество заказов каждого пользователя
SELECT
u.name,
COUNT(o.id) AS orders_count,
COALESCE(SUM(o.amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_spent DESC;
Найти строки без совпадения
-- Пользователи без ни одного заказа
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
После LEFT JOIN строки без совпадения имеют NULL в колонках правой таблицы — это и ловим.
Частые ошибки
INNER JOIN вместо LEFT JOIN — теряются строки:
-- Если хотим всех пользователей включая без заказов
-- INNER JOIN потеряет пользователей без заказов
-- Нужен LEFT JOIN
Дубли из-за JOIN один-ко-многим:
-- Если у пользователя 3 заказа — пользователь появится 3 раза
SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id;
-- Если нужны уникальные пользователи:
SELECT DISTINCT u.name FROM users u JOIN orders o ON u.id = o.user_id;
-- или GROUP BY
Фильтр в WHERE вместо ON при LEFT JOIN:
-- Это эффективно превращает LEFT JOIN в INNER JOIN!
SELECT u.name FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'paid'; -- строки с NULL в o.status отфильтруются
-- Если хотим всех пользователей, но только paid-заказы:
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
Освоив INNER и LEFT JOIN, вы закрываете 95% задач с объединением таблиц.