Как объединить таблицы в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Два способа объединения

  • JOIN — соединяет таблицы по условию (слева направо, добавляет колонки)
  • UNION — складывает строки одной таблицы под другую (сверху вниз, те же колонки)

Чаще нужен JOIN. UNION — когда одинаковая структура, но данные из разных источников.

JOIN — соединение по условию

INNER JOIN

Только строки с совпадением в обеих таблицах:

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

Пользователи без заказов НЕ попадут. Заказы без пользователей тоже.

LEFT JOIN

Все строки из левой таблицы + совпавшие из правой:

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

Пользователи без заказов попадут с NULL в total.

RIGHT JOIN

Наоборот — все из правой, совпавшие из левой. Редко используется (лучше перепишите в LEFT).

FULL OUTER JOIN

Все строки из обеих, NULL где нет пары:

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

CROSS JOIN

Декартово произведение (каждая с каждой):

SELECT u.name, c.country
FROM users u
CROSS JOIN countries c;

Используется редко — обычно для генерации комбинаций.

Визуально

USERS         ORDERS
id | name     user_id | total
 1 | Alice         1  |   100
 2 | Bob           1  |   200
 3 | Carol         4  |    50

INNER JOIN

name  | total
Alice |   100
Alice |   200
(Bob и Carol без заказов → не попадут, orders.user_id=4 тоже нет)

LEFT JOIN

name  | total
Alice |   100
Alice |   200
Bob   | NULL     ← без заказов, но попал
Carol | NULL

1. JOIN двух таблиц

SELECT
    u.id,
    u.name,
    u.email,
    o.order_id,
    o.total,
    o.created_at
FROM users u
INNER JOIN orders o ON o.user_id = u.id;

2. LEFT JOIN с агрегацией

Для каждого пользователя — его статистика (даже если заказов нет):

SELECT
    u.id,
    u.name,
    COUNT(o.order_id) AS orders_cnt,
    COALESCE(SUM(o.total), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;

3. JOIN трёх таблиц

SELECT
    u.name,
    o.order_id,
    p.product_name,
    oi.quantity
FROM users u
JOIN orders o       ON o.user_id = u.id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p     ON p.product_id = oi.product_id;

4. JOIN с условиями в ON

-- только оплаченные заказы
SELECT u.name, o.total
FROM users u
LEFT JOIN orders o
    ON o.user_id = u.id
   AND o.status = 'paid';

Условие в ON (не в WHERE) — чтобы LEFT JOIN не превратился в INNER.

5. Анти-JOIN: пользователи БЕЗ заказов

SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.order_id IS NULL;

Или через NOT EXISTS:

SELECT u.*
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

6. SELF JOIN

Таблица соединяется сама с собой:

-- находим пары сотрудников с одинаковым менеджером
SELECT
    a.name AS emp1,
    b.name AS emp2,
    a.manager_id
FROM employees a
JOIN employees b
    ON a.manager_id = b.manager_id
   AND a.id < b.id;

UNION — объединение строк

UNION vs UNION ALL

  • UNION — удаляет дубликаты (медленнее)
  • UNION ALL — сохраняет все (быстрее)
-- 2023-2026 — все заказы
SELECT * FROM orders_2023
UNION ALL
SELECT * FROM orders_2024
UNION ALL
SELECT * FROM orders_2025
UNION ALL
SELECT * FROM orders_2026;

Правила:

  • Одинаковое количество колонок
  • Одинаковые типы (или совместимые)
  • Порядок колонок имеет значение

7. UNION с пометкой источника

SELECT 'database_a' AS source, id, name FROM users_a
UNION ALL
SELECT 'database_b' AS source, id, name FROM users_b;

8. UNION для вертикального анализа

Статистика разных таблиц в одном результате:

SELECT 'users' AS entity, COUNT(*) AS cnt FROM users
UNION ALL
SELECT 'orders', COUNT(*) FROM orders
UNION ALL
SELECT 'products', COUNT(*) FROM products;

9. JOIN + подзапрос

Агрегируем в подзапросе, потом JOIN:

SELECT
    u.name,
    u.email,
    us.orders_cnt,
    us.total_spent
FROM users u
LEFT JOIN (
    SELECT
        user_id,
        COUNT(*) AS orders_cnt,
        SUM(total) AS total_spent
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id
) us ON us.user_id = u.id;

10. JOIN по нескольким колонкам

SELECT *
FROM orders o
JOIN order_status os
    ON o.order_id = os.order_id
   AND o.created_at >= os.valid_from
   AND o.created_at <  os.valid_to;

Удобно для обработки версий / SCD Type 2.

Частые ошибки

Ошибка 1. INNER вместо LEFT

Теряем пользователей без заказов:

-- неверно, если нужны все юзеры
SELECT * FROM users JOIN orders ...

-- правильно
SELECT * FROM users LEFT JOIN orders ...

Ошибка 2. Условие в WHERE вместо ON для LEFT JOIN

-- превращает LEFT в INNER
SELECT * FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';  -- <- убирает строки с NULL

-- правильно
SELECT * FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid';

Ошибка 3. Cartesian explosion

-- users: 1000 строк, orders: 10000 строк
-- без ON или с неверным ON → 10 млн строк
SELECT * FROM users, orders;

Ошибка 4. Дубликаты после JOIN

Если в правой таблице несколько строк на ключ — строка из левой размножается:

-- один user с 5 заказами → 5 строк
SELECT u.*, o.order_id FROM users u JOIN orders o ON o.user_id = u.id

Если нужны uniq users → GROUP BY или DISTINCT.

Ошибка 5. Забыть алиасы

При самосоединении обязательны алиасы. Вообще — хорошая практика всегда.

Связанные темы

FAQ

JOIN или UNION?

JOIN — когда объединяете колонки из разных таблиц по условию. UNION — когда объединяете строки одинаковой структуры.

LEFT или INNER?

LEFT сохраняет всё из левой таблицы. INNER — только совпадения. Выбирайте по задаче.

UNION или UNION ALL?

UNION ALL быстрее (не дедуплицирует). Используйте его, если дубликаты не проблема или вы уверены, что их нет.

Можно ли JOIN 10 таблиц?

Технически да. Практически — выше 4-5 обычно страдает читаемость и производительность. Используйте CTE или temporary tables.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.