
UNION и UNION ALL: как объединить выгрузки и не потерять повторяющуюся продажу


Если в двух выгрузках продаж встречаются полностью одинаковые строки, главный вопрос не в синтаксисе SQL, а в смысле этих строк: это одна и та же запись, попавшая в оба файла, или два разных события, которые выглядят одинаково из-за грубой детализации данных. От ответа зависит выбор между UNION и UNION ALL. Ниже разберем один узкий сценарий: как объединить учебные выгрузки и проверить, не удалили ли вы реальную продажу вместе с «дублем».
Сначала проверьте, можно ли вообще складывать эти две выгрузки
Для объединения через UNION и UNION ALL запросы должны быть совместимы: возвращать одинаковое число колонок, а соответствующие колонки должны иметь совместимые типы. На практике ошибка чаще возникает не из-за самой операции, а из-за небрежной подготовки источников. В одной выгрузке время может быть текстом, в другой — типом timestamp; в одном файле сумма числом, в другом строкой. Если просто написать SELECT *, вы рискуете объединить столбцы в неправильном порядке или получить неожиданные преобразования.
Поэтому безопаснее сначала привести оба источника к одинаковой структуре явным списком полей. Например, выбрать sale_minute, shop_id, product_id, qty, total_amount в одном и том же порядке и при необходимости использовать приведение типов. Такой шаг полезен еще и потому, что вы фиксируете уровень детализации. Если в одной выгрузке есть receipt_id, а в другой нет, это уже сигнал: после объединения строки могут стать «одинаковыми» только потому, что в одном источнике потерян ключ события.
Есть и второй практический момент. Если вы хотите ограничить одну из частей, например взять только несколько строк для проверки, в PostgreSQL лучше оборачивать отдельный запрос в скобки. Иначе LIMIT может примениться не к источнику, а ко всему результату объединения, и вы будете проверять уже другую выборку.
Почему одинаковая строка не всегда означает лишний дубль
Когда аналитик видит два одинаковых ряда значений, возникает естественное желание убрать повтор. Но SQL не знает, что для вас считается событием. Он сравнивает только набор значений в выбранных колонках. Если две строки совпали по всем полям, UNION оставит одну. Это удобно, когда один и тот же экспорт загрузили дважды. Но это опасно, когда выгрузка не содержит уникального идентификатора продажи.
В учебных продажах такое случается легко. Представьте, что в файлах есть только минута продажи, магазин, товар, количество и сумма. Два покупателя могли взять один и тот же товар в одном магазине в одну и ту же минуту по одной цене. Для бизнеса это два события. Для UNION без дополнительного ключа — одна строка. Значит, удаление повтора в таком наборе данных может занизить количество продаж и выручку, хотя запрос выполнится без ошибок.
Полезная привычка здесь такая: сначала спрашивать не «есть ли повторы», а «достаточно ли у меня полей, чтобы отличить событие от копии файла». Если ответа нет, безопаснее временно сохранить все строки через UNION ALL, добавить признак источника и уже потом разбираться с подозрительными совпадениями.
Учебный пример: одна строка совпала, но это может быть отдельная продажа
Учебный пример. Среда выполнения: PostgreSQL. Допустим, у нас есть две условные выгрузки из одной торговой точки. В них нет receipt_id, поэтому событие определяется только по времени до минуты, товару, количеству и сумме.
WITH
export_a(sale_minute, shop_id, product_id, qty, total_amount) AS (
VALUES
(TIMESTAMP '2026-04-05 10:15:00', 7, 501, 1, 500.00),
(TIMESTAMP '2026-04-05 10:16:00', 7, 502, 2, 600.00),
(TIMESTAMP '2026-04-05 10:18:00', 7, 503, 1, 700.00)
),
export_b(sale_minute, shop_id, product_id, qty, total_amount) AS (
VALUES
(TIMESTAMP '2026-04-05 10:15:00', 7, 501, 1, 500.00),
(TIMESTAMP '2026-04-05 10:19:00', 7, 504, 1, 400.00)
),
union_all_data AS (
SELECT * FROM export_a
UNION ALL
SELECT * FROM export_b
),
union_data AS (
SELECT * FROM export_a
UNION
SELECT * FROM export_b
)
SELECT 'UNION ALL' AS method, COUNT(*) AS rows_cnt, SUM(total_amount) AS total_sum
FROM union_all_data
UNION ALL
SELECT 'UNION' AS method, COUNT(*) AS rows_cnt, SUM(total_amount) AS total_sum
FROM union_data;
Результат на этих данных будет таким:
| Способ объединения | Число строк | Сумма total_amount |
|---|---|---|
| UNION ALL | 5 | 2700.00 |
| UNION | 4 | 2200.00 |
Арифметика здесь проверяется вручную. В первой выгрузке суммы 500 + 600 + 700 = 1800. Во второй 500 + 400 = 900. Если сохранять все строки, общий итог 2700. UNION убирает одну совпавшую строку на 500, поэтому итог становится 2200. Сам SQL здесь не ошибся: он честно удалил дубликат по выбранным полям. Но с точки зрения учета продаж вы могли потерять отдельное событие.
Именно поэтому одинаковый вид строки еще не доказывает, что ее надо удалять. Возможно, это одна и та же продажа, попавшая в два файла. А возможно, это две продажи, которые невозможно различить без недостающего ключа. Вывод из примера не в том, что UNION «плохой», а в том, что он подходит только тогда, когда вы уверены в природе совпадений.
Как проверить, теряете ли вы событие при удалении повторов
Первый контроль — сравнить число строк и сумму до и после удаления повторов. Если после замены UNION ALL на UNION у вас меняется только число строк, но не меняются важные метрики, это еще не означает, что все хорошо. В продажах даже одна удаленная строка может искажать количество чеков, конверсию или частоту покупок. Поэтому проверять нужно не только сумму, но и тот показатель, ради которого строится выгрузка.
Второй контроль — посчитать подозрительные совпадения на объединении с сохранением источника. Тогда одинаковые по бизнес-полям строки не пропадут, и вы сможете их увидеть отдельно.
WITH
export_a(sale_minute, shop_id, product_id, qty, total_amount) AS (
VALUES
(TIMESTAMP '2026-04-05 10:15:00', 7, 501, 1, 500.00),
(TIMESTAMP '2026-04-05 10:16:00', 7, 502, 2, 600.00),
(TIMESTAMP '2026-04-05 10:18:00', 7, 503, 1, 700.00)
),
export_b(sale_minute, shop_id, product_id, qty, total_amount) AS (
VALUES
(TIMESTAMP '2026-04-05 10:15:00', 7, 501, 1, 500.00),
(TIMESTAMP '2026-04-05 10:19:00', 7, 504, 1, 400.00)
),
combined AS (
SELECT 'A' AS src, * FROM export_a
UNION ALL
SELECT 'B' AS src, * FROM export_b
)
SELECT
sale_minute,
shop_id,
product_id,
qty,
total_amount,
COUNT(*) AS row_copies
FROM combined
GROUP BY sale_minute, shop_id, product_id, qty, total_amount
HAVING COUNT(*) > 1;
На учебных данных запрос покажет одну подозрительную комбинацию с row_copies = 2. Это не приговор, а список строк для разбора. Дальше вы проверяете источник данных: есть ли отдельный идентификатор продажи, можно ли поднять секунды вместо минут, не были ли загружены одни и те же файлы повторно. Если такого ключа нет, лучше явно оставить решение в отчете: «совпадающие строки сохранены как отдельные события, потому что идентификатор продажи в выгрузке отсутствует».
Третий контроль — не смешивать очистку и расследование. Частая ошибка выглядит так: сначала применили UNION, потом уже пытаются понять, что исчезло. Но исчезнувшие строки уже не видны. Гораздо надежнее построить промежуточную таблицу через UNION ALL, посчитать частоты совпадений и только после этого решать, на каком основании строку можно схлопнуть.
Практическое правило выбора между UNION и UNION ALL
Если вам нужно сложить результаты как есть, выбирайте UNION ALL. Это базовый вариант для первичной проверки выгрузок, потому что он ничего не скрывает. Затем отдельно ищите совпавшие строки и объясняйте их происхождение. Если же вы точно знаете, что два источника могут содержать одну и ту же запись в одном и том же уровне детализации, и у вас есть основание считать совпадение техническим дублем, тогда можно переходить к UNION.
Практически полезный вывод здесь такой: перед финальным объединением держите короткий чек-лист. Совпадает ли число колонок и их порядок? Совместимы ли типы? Есть ли в данных ключ события? Меняются ли после удаления повторов количество строк, сумма и другие важные метрики? Если хотя бы на один вопрос нет уверенного ответа, не удаляйте совпадения автоматически. Сначала сохраните их через UNION ALL и проверьте отдельно.
Если хотите отработать именно такие задачи, в сохраненном описании каталога курса SQL для анализа данных перечислены темы по SELECT, фильтрации, агрегатам, JOIN, работе с NULL и объединению с помощью UNION. Для разбора выгрузок это полезная связка: сначала привести поля к одному виду, потом объединить, затем проверить результат по строкам и итоговым суммам.





















