JOIN
JOIN нужен, чтобы объединять строки из двух (и более) таблиц по условию связи.
Обычно условие связи выглядит так: ON a.id = b.a_id.
В JOIN всегда есть две части: тип соединения (INNER/LEFT/...) и условие ON. Большинство ошибок — неправильный ON или неправильный тип JOIN.
Пример 1 — INNER JOIN
INNER JOIN возвращает только те строки, где нашлась пара по условию ON.
Если пары нет — строка не попадёт в результат.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total | status |
|---|---|---|---|
| 10 | 1 | 120 | done |
| 11 | 1 | 80 | new |
| 12 | 2 | 50 | done |
| 13 | NULL | 70 | new |
Запрос:
SELECT u.name, o.id AS order_id, o.total, o.status
FROM users u
JOIN orders o ON o.user_id = u.id
ORDER BY u.id, o.id;
| name | order_id | total | status |
|---|---|---|---|
| Alice | 10 | 120 | done |
| Alice | 11 | 80 | new |
| Bob | 12 | 50 | done |
Потому что orders.user_id = NULL, а сравнение NULL = ... никогда не истинно.
Совпадения по ON o.user_id = u.id нет.
Пример 2 — LEFT JOIN
LEFT JOIN возвращает все строки из левой таблицы и присоединяет совпадения справа.
Если совпадений нет — поля справа будут NULL.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total | status |
|---|---|---|---|
| 10 | 1 | 120 | done |
| 11 | 1 | 80 | new |
| 12 | 2 | 50 | done |
| 13 | NULL | 70 | new |
Запрос:
SELECT u.name, o.id AS order_id, o.total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
ORDER BY u.id, o.id;
| name | order_id | total |
|---|---|---|
| Alice | 10 | 120 |
| Alice | 11 | 80 |
| Bob | 12 | 50 |
| Carol | NULL | NULL |
Пример 3 — ON vs WHERE в LEFT JOIN
Это самая частая ловушка: фильтрация по правой таблице в WHERE “убивает” строки без совпадений.
3.1 Условие в WHERE (плохой вариант, если “все слева должны остаться”)
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total | status |
|---|---|---|---|
| 10 | 1 | 120 | done |
| 11 | 1 | 80 | new |
| 12 | 2 | 50 | done |
| 13 | NULL | 70 | new |
Запрос:
SELECT u.name, o.id, o.status
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 'done'
ORDER BY u.id, o.id;
| name | id | status |
|---|---|---|
| Alice | 10 | done |
| Bob | 12 | done |
У Carol нет заказов → справа поля NULL.
Условие o.status = 'done' ложно для NULL → строка отфильтровалась.
3.2 Условие в ON (правильный вариант)
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total | status |
|---|---|---|---|
| 10 | 1 | 120 | done |
| 11 | 1 | 80 | new |
| 12 | 2 | 50 | done |
| 13 | NULL | 70 | new |
Запрос:
SELECT u.name, o.id, o.status
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
AND o.status = 'done'
ORDER BY u.id, o.id;
| name | id | status |
|---|---|---|
| Alice | 10 | done |
| Bob | 12 | done |
| Carol | NULL | NULL |
Пример 4 — “все справа” (аналог RIGHT JOIN)
В SQLite проще мыслить так: если нужно “все строки из orders + пользователь, если есть” —
просто делай orders левой таблицей в LEFT JOIN.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total | status |
|---|---|---|---|
| 10 | 1 | 120 | done |
| 11 | 1 | 80 | new |
| 12 | 2 | 50 | done |
| 13 | NULL | 70 | new |
Запрос:
SELECT o.id, o.total, u.name
FROM orders o
LEFT JOIN users u ON u.id = o.user_id
ORDER BY o.id;
| id | total | name |
|---|---|---|
| 10 | 120 | Alice |
| 11 | 80 | Alice |
| 12 | 50 | Bob |
| 13 | 70 | NULL |
Пример 5 — CROSS JOIN
CROSS JOIN соединяет каждую строку A с каждой строкой B (декартово произведение).
Используется редко, но важно знать, чтобы случайно не получить огромный результат.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| status |
|---|
| new |
| done |
Запрос:
SELECT u.name, s.status
FROM users u
CROSS JOIN statuses s
ORDER BY u.id, s.status;
| name | status |
|---|---|
| Alice | done |
| Alice | new |
| Bob | done |
| Bob | new |
При больших таблицах результат растёт как произведение размеров. В тренажёре такие запросы могут сильно грузить сервер.
Пример 6 — JOIN + агрегаты (GROUP BY)
Частый реальный кейс: посчитать количество заказов и сумму по каждому пользователю.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total |
|---|---|---|
| 10 | 1 | 120 |
| 11 | 1 | 80 |
| 12 | 2 | 50 |
Запрос:
SELECT
u.name,
COUNT(o.id) AS orders_count,
COALESCE(SUM(o.total), 0) AS total_sum
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY u.id;
| name | orders_count | total_sum |
|---|---|---|
| Alice | 2 | 200 |
| Bob | 1 | 50 |
| Carol | 0 | 0 |
Для пользователя без заказов SUM(o.total) будет NULL, поэтому превращаем в 0:
COALESCE(SUM(...), 0).
Пример 7 — Anti-join (найти “без пары”)
Задача: найти пользователей, у которых нет заказов.
Делается через LEFT JOIN и проверку IS NULL по ключу правой таблицы.
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id | total |
|---|---|---|
| 10 | 1 | 120 |
| 11 | 1 | 80 |
| 12 | 2 | 50 |
Запрос:
SELECT u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
| name |
|---|
| Carol |
Пример 8 — Self JOIN (таблица соединяется сама с собой)
Полезно для иерархий: сотрудники и их менеджеры.
Данные:
| id | name | manager_id |
|---|---|---|
| 1 | Alice | NULL |
| 2 | Bob | 1 |
| 3 | Carol | 1 |
| 4 | Dave | 2 |
Запрос:
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id
ORDER BY e.id;
| employee | manager |
|---|---|
| Alice | NULL |
| Bob | Alice |
| Carol | Alice |
| Dave | Bob |
Частые ошибки при JOIN
- Неправильное условие связи (
ONне по ключам) → “лишние” строки или пустой результат. - Забыть алиасы при одинаковых названиях столбцов → непонятно, из какой таблицы поле.
- Фильтровать правую таблицу через WHERE при
LEFT JOIN→ превращение в INNER JOIN. - Случайный CROSS JOIN → взрыв количества строк и тормоза.
- Дубликаты при JOIN: если справа несколько строк на одну слева — строк станет больше (это не баг).
Итого
INNER JOIN— только совпавшие пары.LEFT JOIN— все строки слева + совпадения справа (или NULL).- В
LEFT JOINусловия на правую таблицу часто надо писать вON, а не вWHERE. - JOIN отлично сочетается с агрегатами (
GROUP BY). - Anti-join:
LEFT JOIN+WHERE right.id IS NULL→ “найти без пары”.