UNION и UNION ALL в SQL: как объединять результаты запросов
Когда использовать UNION и UNION ALL: объединение событий, каналов и архивных таблиц, требования к колонкам и риск незаметной дедупликации.
Содержание статьи
UNION и UNION ALL выглядят почти одинаково, но первый удаляет дубли, а второй сохраняет все строки. Для событий и заказов это принципиальная разница: одинаковые значения колонок не означают, что строки описывают один и тот же факт. Разберём вопрос на одном запросе: Как объединить два набора строк и не потерять легитимные повторы?
Когда нужен этот приём
Как объединить два набора строк и не потерять легитимные повторы?
Если нужно собрать события из web_events и app_events, добавь source и используй UNION ALL. Если объединяются справочники с возможным пересечением и нужна уникальная пара code/name, UNION может быть оправдан. Но даже тогда документируй, почему дубликат считается одной сущностью.
Для независимых потоков фактов обычно нужен UNION ALL. Для действительно одинаковых строк можно применить UNION, но лучше сначала проверить качество источников. Для связывания признаков одной сущности выбирай JOIN.
Логика оператора
Обе части UNION должны иметь одинаковое число колонок в совместимых типах и одинаковый смысл позиций. UNION выполняет дедупликацию результата, UNION ALL просто складывает наборы. Для больших данных UNION ALL предсказуемее, если уникальность обеспечивается ключом или нужна отдельно.
Сначала выровняй схему: одинаковые имена через alias, явные CAST и источник строки. Проверь зерно каждого набора. После объединения посчитай строки, уникальные ключи и пересечения по периодам. Не используй SELECT *: изменение схемы одной таблицы может сломать контракт.
- Зафиксируй одну строку результата.
- Отдели условия по исходным строкам от условий по рассчитанным значениям.
- Назови период, ключи связи и правило для повторов.
Рабочая версия
Названия таблиц здесь условные. При переносе сохрани зерно и бизнес-условия, а не буквальный синтаксис примера.
SELECT user_id, event_name, occurred_at, 'web' AS source
FROM web_events WHERE occurred_at >= DATE '2026-07-01'
UNION ALL
SELECT user_id, event_name, occurred_at, 'app' AS source
FROM app_events WHERE occurred_at >= DATE '2026-07-01';Как сверить ответ
Собери пять-десять строк, где ответ можно получить вручную. Добавь NULL, дубль, объект без связанной записи и значение на границе периода. Затем сравни число строк и уникальных ключей до и после спорного шага.
Выполненный без ошибки запрос ещё не является правильным. Другой аналитик должен суметь восстановить из него источник, фильтр, grain и ожидаемое поведение на крайних случаях.
| Ситуация | Выбор | Почему |
|---|---|---|
| независимые события | UNION ALL | повторы могут быть реальными |
| архив + текущая таблица | проверка пересечения | не дублировать период |
| уникальный список кодов | UNION | дубликаты не нужны |
| общие атрибуты сущности | JOIN | нужно добавить колонки |
Когда выбрать другой способ
UNION не заменяет JOIN: он добавляет строки, а не колонки. Порядок колонок важнее их имён. UNION может удалить реальные повторы. UNION ALL может удвоить период, если источники пересекаются. Даты и numeric-типы лучше приводить явно.
Для независимых потоков фактов обычно нужен UNION ALL. Для действительно одинаковых строк можно применить UNION, но лучше сначала проверить качество источников. Для связывания признаков одной сущности выбирай JOIN.
Количество строк после объединения равно сумме частей, если нет дополнительной дедупликации.
Небольшой эксперимент
Объедини web и mobile события за месяц с полем source. Затем добавь одну общую строку и сравни UNION с UNION ALL. В отчёте объясни, какой ключ отвечает за уникальность события и где должна происходить дедупликация.
- Сначала запиши ожидаемый результат словами.
- Проверь запрос на маленькой контрольной выборке.
- Объясни, что запрос считает и чего не доказывает.
Следующий шаг
Для независимых потоков фактов обычно нужен UNION ALL. Для действительно одинаковых строк можно применить UNION, но лучше сначала проверить качество источников. Для связывания признаков одной сущности выбирай JOIN.
Материалы по теме
Инкрементальная загрузка данных в SQL: watermark и поздние события
Как организовать инкрементальный расчёт по дате, учитывать late-arriving events и не терять исправления в источнике.
COUNT(*) и COUNT(column) в SQL: почему NULL меняет количество
Разбираем разницу COUNT(*), COUNT(column) и COUNT DISTINCT на пропусках, пользователях и денежных фактах.
Интервал между событиями в SQL: LAG и время до следующего шага
Как посчитать время между действиями пользователя через LAG, найти длинные паузы и связать их с конверсией.