← Назад к вопросам

Что такое JOIN?

1.0 Junior🔥 301 комментариев
#SQL и базы данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SQL JOIN: полное руководство

JOIN — один из самых важных инструментов в SQL. Это основа для объединения данных из разных таблиц.

Что такое JOIN?

JOIN — это операция, которая комбинирует строки из двух (или более) таблиц на основе условия связи.

Просто говоря: я беру строки из таблицы A, ищу соответствующие строки в таблице B, и объединяю их.

Пример в реальной жизни:

Таблица Users:
id | name      | city
1  | Alice     | NYC
2  | Bob       | LA
3  | Charlie   | NYC

Таблица Orders:
id | user_id | amount
101| 1       | $100
102| 1       | $200
103| 2       | $150
104| 3       | $300

Что хочу: Вывести имя пользователя и его заказы

Что нужно: JOIN на user_id

Основные типы JOIN

1. INNER JOIN (пересечение)

SELECT u.id, u.name, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

Результат:
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
2  | Bob     | 150
3  | Charlie | 300

Что здесь: только пользователи ЧТО ИМЕЮТ заказы
Charlie (id=3) есть!

2. LEFT JOIN (левая таблица + пересечение)

SELECT u.id, u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

Результат:
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
2  | Bob     | 150
3  | Charlie | 300

Сейчас это выглядит как INNER JOIN, потому что у всех есть заказы.
НО если у кого-то нет заказов → будет NULL.

Пример с NULL:

-- Добавил нового пользователя без заказов
INSERT INTO users VALUES (4, 'Diana', 'Boston');

SELECT u.id, u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

Результат:
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
2  | Bob     | 150
3  | Charlie | 300
4  | Diana   | NULL  ← Diana нет заказов!

# LEFT JOIN сохраняет ВСЕ строки из LEFT таблицы,
# даже если в RIGHT нет соответствия

3. RIGHT JOIN (правая таблица + пересечение)

SELECT u.id, u.name, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

Результат:
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
2  | Bob     | 150
3  | Charlie | 300

# RIGHT JOIN сохраняет ВСЕ строки из RIGHT таблицы (orders)
# Если заказ не связан с пользователем → user id = NULL

4. FULL OUTER JOIN (всё объединяю)

SELECT u.id, u.name, o.amount
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

Результат:
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
2  | Bob     | 150
3  | Charlie | 300
4  | Diana   | NULL  ← пользователь без заказов

# FULL OUTER = LEFT + RIGHT
# Все пользователи + все заказы, даже если нет соответствия

5. CROSS JOIN (декартово произведение)

SELECT u.id, u.name, o.amount
FROM users u
CROSS JOIN orders o;

-- ИЛИ эквивалентно
SELECT u.id, u.name, o.amount
FROM users u, orders o;

Результат: каждый пользователь с КАЖДЫМ заказом!
id | name    | amount
1  | Alice   | 100
1  | Alice   | 200
1  | Alice   | 150
1  | Alice   | 300
2  | Bob     | 100
2  | Bob     | 200
... (4 users × 4 orders = 16 строк!)

# Обычно CROSS JOIN нежелателен (случайно)
# Но иногда нужен: для cartesian product

Визуализация JOIN'ов (диаграммы Венна)

INNER JOIN          LEFT JOIN
A  ∩  B            A  |  (A ∩ B)
   ███                  ███████
                        ███

RIGHT JOIN          FULL OUTER
(A ∩ B)  |  B      A  |  B  |  A
███████              ███  ███  ███
   ███

Условия JOIN

На одинаковые ID (основной случай):

SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id;

На разные колонки:

SELECT *
FROM orders o
JOIN cities c ON o.city_name = c.name;

На несколько условий:

SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id AND o.country = u.country;

На диапазоны:

SELECT *
FROM purchases p
JOIN promotions pr ON p.purchase_amount BETWEEN pr.min_amount AND pr.max_amount;

Практические примеры

Пример 1: Продажи по регионам

SELECT 
  r.region_name,
  COUNT(o.id) as total_orders,
  SUM(o.amount) as total_revenue
FROM regions r
LEFT JOIN stores s ON r.id = s.region_id
LEFT JOIN orders o ON s.id = o.store_id
GROUP BY r.region_name
ORDER BY total_revenue DESC;

-- LEFT JOIN использую, чтобы показать регионы БЕЗ заказов
-- (с 0 доходом)

Пример 2: Найти пользователей БЕЗ заказов

SELECT u.id, u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;  ← ключевой момент!

-- LEFT JOIN + WHERE IS NULL = антиджой
-- Все из LEFT которые НЕ совпали с RIGHT

Пример 3: Сравнение всех заказов

SELECT 
  o1.id as order_1_id,
  o2.id as order_2_id,
  o1.amount as amount_1,
  o2.amount as amount_2,
  o1.amount - o2.amount as difference
FROM orders o1
JOIN orders o2 ON o1.user_id = o2.user_id 
  AND o1.id < o2.id  ← чтобы не повторять пары
WHERE o1.amount != o2.amount;

-- Self-join (присоединяю таблицу к самой себе)

Пример 4: Цепочка JOIN'ов

SELECT 
  o.id as order_id,
  u.name as customer_name,
  p.name as product_name,
  c.name as category_name,
  o.amount
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 categories c ON p.category_id = c.id;

-- 4 JOIN'а для соединения 5 таблиц
-- Порядок важен для производительности

Производительность JOIN

Индексы критичны:

-- Медленно (без индекса)
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;
-- Время: 5 сек

-- Быстро (с индексом)
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_users_id ON users(id);

SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;
-- Время: 0.1 сек

Правило: Индексируй foreign keys для JOIN'ов

Типичные ошибки

Ошибка 1: Забыл условие JOIN

-- ❌ НЕПРАВИЛЬНО
SELECT * FROM orders o
JOIN users u;
-- CROSS JOIN! 10M × 1M = 10B строк
-- Время: forever, сломаешь базу

-- ✅ ПРАВИЛЬНО
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;

Ошибка 2: Неправильный JOIN тип

-- ❌ НЕПРАВИЛЬНО (теряю данные)
SELECT * FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- Пользователи БЕЗ заказов не появляются

-- ✅ ПРАВИЛЬНО
SELECT * FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

Ошибка 3: Фильтр в JOIN вместо WHERE

-- ❌ НЕПРАВИЛЬНО (нарушает LEFT JOIN)
SELECT * FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.amount > 100;
-- Пользователи без заказов всё равно будут, но
-- большие заказы будут отфильтрованы корректно

-- ✅ ПРАВИЛЬНО (если хочу только больших заказов)
SELECT * FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;
-- Теперь пользователи БЕЗ заказов исчезнут

Как выбрать правильный JOIN

Алгоритм:

1. Вопрос: "Все ли записи из LEFT таблицы должны быть?"
   Да → LEFT JOIN
   Нет → INNER JOIN

2. Вопрос: "Нужны ли записи из RIGHT без соответствия в LEFT?"
   Да → FULL OUTER JOIN
   Нет → готов

3. Вопрос: "Мне нужно все комбинации?"
   Да → CROSS JOIN
   Нет → готов

Вывод

JOIN — это мост между таблицами. Правильный выбор типа JOIN критичен:

  • INNER — только совпадения
  • LEFT — все из левой + совпадения
  • RIGHT — все из правой + совпадения
  • FULL OUTER — всё
  • CROSS — все комбинации

Использую LEFT JOIN по умолчанию и добавляю индексы на foreign keys для производительности.