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 и в 2026EXCEPT (или 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).