UNION SQL: шпаргалка для собеседования

Зачем аналитику UNION

UNION объединяет результаты двух SELECT по строкам (склейка vertical). Нужен, когда хочешь смешать данные из разных таблиц или сценариев в один результирующий набор.

Базовый синтаксис

SELECT col1, col2 FROM table_a
UNION
SELECT col1, col2 FROM table_b;

Обязательно:

  • Одинаковое количество столбцов в обоих SELECT.
  • Совместимые типы в каждой позиции.
  • Имена столбцов берутся из первого SELECT.

UNION vs UNION ALL

-- UNION удаляет дубликаты (медленнее)
SELECT 1 UNION SELECT 1 UNION SELECT 2;
-- Результат: 1, 2

-- UNION ALL оставляет все (быстрее)
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 2;
-- Результат: 1, 1, 2

Главное правило: если дубликатов заведомо нет — используй UNION ALL. Он быстрее, потому что не делает внутреннюю дедупликацию.

На собесе: «Когда использовать UNION vs UNION ALL?» Ответ: UNION ALL когда уверен, что дублей не будет (например, непересекающиеся условия). UNION — когда нужна дедупликация.

INTERSECT и EXCEPT

Родственники UNION, используются реже, но спрашивают.

INTERSECT — пересечение

SELECT user_id FROM orders_2025
INTERSECT
SELECT user_id FROM orders_2026;
-- Пользователи, которые покупали и в 2025 и в 2026

EXCEPT (или MINUS в Oracle)

SELECT user_id FROM users
EXCEPT
SELECT user_id FROM orders;
-- Пользователи без заказов (аналог LEFT JOIN + IS NULL)

Тренироваться на таких вопросах можно в Telegram-боте Карьерник — там 1500+ задач с реальных собесов с разборами.

Порядок столбцов имеет значение

-- ❌ Типы не совпадают — ошибка
SELECT 'Ivan', 25 UNION SELECT 30, 'Petr';

-- ✅ Порядок должен соответствовать
SELECT 'Ivan', 25 UNION SELECT 'Petr', 30;

Проверяется по позиции, не по имени — это частый источник багов.

Имена столбцов

SELECT name AS user_name FROM users_a
UNION
SELECT full_name FROM users_b;

-- Результирующий столбец: user_name (имя из первого SELECT)

ORDER BY с UNION

-- ✅ В конце
SELECT x FROM a
UNION
SELECT x FROM b
ORDER BY x;

-- ❌ Внутри отдельного SELECT — бессмысленно
SELECT x FROM a ORDER BY x UNION SELECT x FROM b;

ORDER BY применяется ко всему результату UNION, поэтому ставится в конце.

LIMIT с UNION

-- LIMIT в одной части
(SELECT x FROM a LIMIT 10)
UNION ALL
(SELECT x FROM b LIMIT 10);

-- LIMIT на весь результат
SELECT x FROM a UNION ALL SELECT x FROM b LIMIT 20;

Скобки помогают явно разделить области действия.

Классические кейсы

1. Разные сущности под одной шапкой

SELECT 'ORDER' AS type, order_id AS id, created_at FROM orders
UNION ALL
SELECT 'return' AS type, return_id AS id, created_at FROM returns
ORDER BY created_at DESC;

Единый лог событий из двух таблиц.

2. Шахматная доска из двух регионов

SELECT 'RU' AS region, user_id, amount FROM orders_ru
UNION ALL
SELECT 'BY' AS region, user_id, amount FROM orders_by;

Затем GROUP BY region — получим статистику по регионам одним запросом.

3. Эмуляция FULL OUTER JOIN

SELECT a.id, a.val, b.val FROM a LEFT JOIN b ON a.id = b.id
UNION ALL
SELECT b.id, a.val, b.val FROM b LEFT JOIN a ON a.id = b.id WHERE a.id IS NULL;

В СУБД без FULL OUTER JOIN (старые MySQL) — классический приём.

К слову, набить руку на таких кейсах удобно через тренажёр в Telegram — разбирайте по 10 вопросов в день, через 2 недели тема становится рефлексом.

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

  • UNION ALL быстрее UNION всегда — нет sort + dedup.
  • Индексы сохраняются: каждый SELECT использует свой индекс.
  • Большой UNION (5+ частей) — часто признак плохого дизайна. Проверьте, нет ли способа объединить в одну таблицу с меткой типа.

10 задач с собеседований

1. Объединить активных и неактивных пользователей

SELECT user_id, 'active' AS status FROM users WHERE last_active >= CURRENT_DATE - INTERVAL '30 day'
UNION ALL
SELECT user_id, 'inactive' FROM users WHERE last_active < CURRENT_DATE - INTERVAL '30 day';

2. Клиенты, которые покупали и в 2025, и в 2026

SELECT user_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2025
INTERSECT
SELECT user_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2026;

3. Клиенты без заказов

SELECT user_id FROM users
EXCEPT
SELECT user_id FROM orders;

4. Все события в одну ленту

SELECT 'signup' AS event, user_id, registered_at AS ts FROM users
UNION ALL
SELECT 'ORDER', user_id, created_at FROM orders
UNION ALL
SELECT 'payment', user_id, paid_at FROM payments
ORDER BY ts;

5. Распределение по регионам

WITH all_orders AS (
    SELECT 'RU' AS region, amount FROM ru.orders
    UNION ALL
    SELECT 'BY', amount FROM BY.orders
)
SELECT region, SUM(amount) FROM all_orders GROUP BY region;

6. Удалить дубликаты из двух источников

SELECT * FROM source_a
UNION
SELECT * FROM source_b;

UNION сам дедуплицирует — если данные идентичны, получим чистый список.

7. Десктоп + мобайл → один канал «web»

SELECT user_id, amount, 'web' AS channel FROM orders WHERE platform IN ('desktop', 'mobile_web')
UNION ALL
SELECT user_id, amount, 'app' FROM orders WHERE platform = 'app';

8. Тестовая и продакшн-таблицы

SELECT user_id, amount, 'test' AS env FROM test.orders
UNION ALL
SELECT user_id, amount, 'prod' FROM prod.orders;

9. Дополнить данными из истории

SELECT user_id, amount, created_at FROM orders
UNION ALL
SELECT user_id, amount, created_at FROM orders_archive;

10. Сравнить top-5 из двух когорт

SELECT 'cohort_a' AS c, user_id, revenue FROM cohort_a_top5
UNION ALL
SELECT 'cohort_b', user_id, revenue FROM cohort_b_top5
ORDER BY revenue DESC;

Как тренироваться

UNION — простая тема, но на собесе ловят через тонкие вопросы (UNION vs UNION ALL, порядок столбцов, INTERSECT). Знание разницы между UNION и UNION ALL — обязательный минимум.

Тренажёр Карьерник содержит задачи на UNION, INTERSECT, EXCEPT — с разборами типичных ошибок.

Совет: по умолчанию пишите UNION ALL. UNION нужен только когда знаете, что дубликаты есть и их надо убрать. В 90% задач UNION ALL — правильный выбор.

Читайте также

FAQ

UNION или UNION ALL по умолчанию?

UNION ALL быстрее. UNION только если нужна дедупликация. На собесе этот вопрос задают в каждом втором интервью — отвечайте UNION ALL.

Когда использовать INTERSECT?

Когда нужны элементы, присутствующие в обеих выборках (пересечение). Аналог WHERE x IN (SELECT x FROM other), но короче и чище.

Почему UNION с ORDER BY внутри даёт ошибку?

ORDER BY применяется к итоговому UNION-результату, а не к каждому SELECT отдельно. Либо выносите ORDER BY в конец, либо оборачивайте части в скобки с LIMIT.

FULL OUTER JOIN через UNION — быстрее или медленнее?

Обычно медленнее — FULL OUTER JOIN оптимизирован на уровне СУБД. UNION-эмуляция нужна только если СУБД не поддерживает FULL OUTER (старый MySQL).