JOIN: объединение таблиц
JOIN: объединение таблиц
В реляционных БД данные разнесены по разным таблицам. Чтобы получить полную картину — например, имя пользователя и его заказы вместе — нужно объединять таблицы. Для этого служит JOIN.
Зачем нужен JOIN
Представьте: вам нужно узнать имя пользователя, сделавшего заказ №15. Эти данные в разных таблицах:
orders: users:
| id | user_id | total | | id | name | email |
|----|---------|-------| |----|-------|----------------|
| 15 | 3 | 2500 | | 3 | Мария | m@example.com |
JOIN соединяет их по общему полю (orders.user_id = users.id):
SELECT orders.id, users.name, orders.total
FROM orders
JOIN users ON orders.user_id = users.id
WHERE orders.id = 15;
-- Результат:
-- id | name | total
-- ---+-------+------
-- 15 | Мария | 2500
INNER JOIN
INNER JOIN (или просто JOIN) возвращает только те строки, для которых есть совпадение в обеих таблицах.
SELECT o.id, u.name, o.status, o.total
FROM orders o
INNER JOIN users u ON o.user_id = u.id;
Пользователи без заказов — не попадут в результат. Заказы без пользователя (если FK не enforced) — тоже.
LEFT JOIN
LEFT JOIN возвращает все строки из левой таблицы (FROM) и совпадающие строки из правой. Если совпадения нет — правая часть заполняется NULL.
-- Все пользователи, у каждого — его заказы (или NULL если заказов нет)
SELECT u.id, u.name, o.id AS order_id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- Результат:
-- id | name | order_id | total
-- ---+-------+----------+------
-- 1 | Анна | 1 | 5000
-- 1 | Анна | 2 | 1200
-- 2 | Пётр | 3 | 800
-- 4 | Игорь | NULL | NULL ← нет заказов
Поиск «осиротевших» записей
Очень полезный паттерн для тестировщика — найти записи без связанных данных:
-- Пользователи, у которых нет ни одного заказа
SELECT u.id, u.name, u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
RIGHT JOIN
RIGHT JOIN — зеркало LEFT JOIN: возвращает все строки из правой таблицы и совпадающие из левой. На практике используется редко — обычно меняют порядок таблиц и пишут LEFT JOIN.
-- Все заказы, у каждого — пользователь (или NULL)
SELECT u.name, o.id, o.total
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
FULL OUTER JOIN
FULL OUTER JOIN возвращает все строки из обеих таблиц. Где нет совпадения — NULL с соответствующей стороны.
SELECT u.name, o.id AS order_id
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;
Используется для поиска несвязанных записей с обеих сторон.
JOIN трёх и более таблиц
Можно объединять сколько угодно таблиц — каждый JOIN добавляет одну таблицу.
-- Заказы с именем пользователя и списком товаров
SELECT
u.name AS user_name,
o.id AS order_id,
o.status,
p.name AS product_name,
oi.quantity,
oi.price
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id
WHERE o.id = 15;
Псевдонимы таблиц
При JOIN таблицам дают короткие псевдонимы, чтобы не писать полное имя каждый раз:
-- Без псевдонимов — громоздко
SELECT orders.id, users.name FROM orders JOIN users ON orders.user_id = users.id;
-- С псевдонимами — читаемо
SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id;
Итоговое сравнение JOIN
| Тип | Что возвращает |
|---|---|
INNER JOIN | Только строки с совпадением в обеих таблицах |
LEFT JOIN | Все строки из левой + совпадения из правой (NULL если нет) |
RIGHT JOIN | Все строки из правой + совпадения из левой (NULL если нет) |
FULL OUTER JOIN | Все строки из обеих таблиц (NULL где нет совпадений) |
Практические задачи для тестировщика
-- Последние 10 заказов с именем пользователя
SELECT u.name, o.id, o.status, o.total, o.created_at
FROM orders o
JOIN users u ON o.user_id = u.id
ORDER BY o.created_at DESC
LIMIT 10;
-- Товары, которые ни разу не заказывали
SELECT p.id, p.name, p.category
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id
WHERE oi.id IS NULL;
-- Состав конкретного заказа
SELECT p.name, oi.quantity, oi.price, (oi.quantity * oi.price) AS subtotal
FROM order_items oi
JOIN products p ON oi.product_id = p.id
WHERE oi.order_id = 42;
-- Пользователи с незавершёнными заказами
SELECT DISTINCT u.id, u.name, u.email
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'pending';
Итоги
- JOIN объединяет строки из нескольких таблиц по условию связи (
ON) - INNER JOIN — только совпадающие строки из обеих таблиц
- LEFT JOIN — все строки из левой таблицы + совпадения из правой (NULL если нет)
- RIGHT JOIN — зеркало LEFT JOIN (на практике используется редко)
- FULL OUTER JOIN — все строки из обеих таблиц
- Паттерн
LEFT JOIN ... WHERE правая.id IS NULL— находит записи без связанных данных - Псевдонимы (
o,u,p) делают многотабличные запросы читаемыми