JOIN склеивает строки двух таблиц по условию. Представьте: для каждой пары строк (левая, правая) проверяется условие из ON. Пары, где оно true, попадают в результат.
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id;
Виды JOIN
- INNER JOIN (или просто
JOIN): только пары, где условие выполнено. Заказ без клиента и клиент без заказов пропадут. - LEFT JOIN: все строки левой таблицы. Если пары справа нет, правые колонки заполняются NULL.
- RIGHT JOIN: зеркально, все строки правой. На практике переписывают в LEFT, поменяв таблицы местами.
- FULL JOIN: все строки обеих таблиц, недостающая сторона NULL.
- CROSS JOIN: декартово произведение, каждая строка с каждой. 100 × 100 = 10 000 строк.
Размножение строк
JOIN не «подтягивает одно значение»: если у клиента 3 заказа, клиент появится в результате 3 раза. Если затем приджойнить ещё таблицу «один ко многим» (например, отзывы), строки перемножатся: 3 заказа × 2 отзыва = 6 строк. Отсюда классический баг: SUM(order.total) после двух JOIN считает каждый заказ несколько раз.
Лечится агрегацией до JOIN (в подзапросе или CTE) или отдельными подзапросами.
ON против WHERE в LEFT JOIN
Для INNER JOIN разницы почти нет. Для LEFT JOIN огромная:
-- Все клиенты и их оплаченные заказы (у кого нет, будет NULL)
SELECT c.name, o.id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid';
-- Ошибка: WHERE выкинет строки с NULL, LEFT превратился в INNER
SELECT c.name, o.id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'paid';
Правило: условие на правую таблицу LEFT JOIN, которое должно фильтровать «что приклеить», пишите в ON.
Anti-join: «у кого нет»
Найти клиентов без заказов:
SELECT c.*
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
Равноценная и часто более читаемая форма: WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id).
Self join
Таблица соединяется сама с собой через разные псевдонимы. Классика: сотрудник и его руководитель.
SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;
USING и NATURAL
JOIN orders USING (customer_id) работает, когда колонки называются одинаково. NATURAL JOIN соединяет по всем одноимённым колонкам: опасно, добавили колонку created_at в обе таблицы, и запрос молча сломался.
Частые ошибки
- Фильтр по правой таблице LEFT JOIN в WHERE.
- Неучтённое размножение строк перед SUM или COUNT.
- Забытое условие соединения, получился CROSS JOIN.
- NULL в ключе соединения:
NULL = NULLне true, такие строки не склеятся.
Что спросят на собесе
- Чем отличаются INNER, LEFT, FULL и CROSS JOIN, сколько строк будет в результате.
- Как найти записи без пары в другой таблице.
- Почему условие в WHERE ломает LEFT JOIN.
- Почему после JOIN сумма получилась больше, чем должна.
- Как вывести сотрудника вместе с именем руководителя.