Cet article est actuellement en russe — la traduction en anglais est en cours.
В SQL есть два популярных способа объединять данные из разных запросов:
UNION
и:
UNION ALL
На первый взгляд они почти одинаковые. Оба позволяют взять результат одного SELECT и добавить под него результат другого SELECT.
То есть строки не соединяются рядом, как в JOIN, а складываются вертикально — одна выборка под другой.
Например, у нас есть пользователи из одной таблицы и клиенты из другой. Мы хотим получить один общий список email. В такой ситуации как раз может пригодиться UNION или UNION ALL.
Но между ними есть важная разница:
UNION ALL оставляет все строки, включая дубликаты;
UNION убирает повторяющиеся строки.
Эта разница влияет не только на результат, но и на скорость запроса. Иногда UNION делает полезную работу, а иногда просто тратит ресурсы на ненужную дедупликацию.
Разберёмся спокойно и на примерах.
Что делает UNION простыми словами
Представим два запроса.
Первый возвращает страны пользователей:
SELECT country
FROM users;
Результат:
country
--------
Vietnam
Germany
France
Второй возвращает страны сотрудников:
SELECT country
FROM employees;
Результат:
country
--------
Germany
Spain
Vietnam
Если мы хотим получить один общий список стран, можно объединить результаты:
SELECT country
FROM users
UNION
SELECT country
FROM employees;
Результат будет таким:
country
--------
Vietnam
Germany
France
Spain
Обратите внимание: Vietnam и Germany были в обеих выборках, но в итоговом результате остались по одному разу.
Так работает UNION: он объединяет строки и убирает дубликаты.
Что делает UNION ALL
Теперь возьмём тот же пример, но используем UNION ALL.
SELECT country
FROM users
UNION ALL
SELECT country
FROM employees;
Результат будет другим:
country
--------
Vietnam
Germany
France
Germany
Spain
Vietnam
Здесь все строки остались на месте.
Если Germany была в первой выборке и во второй, она появится два раза.
Если Vietnam была в первой выборке и во второй, она тоже появится два раза.
UNION ALL ничего не удаляет. Он просто берёт строки из первого запроса и дописывает под них строки из второго.
Можно запомнить так:
UNION ALL — сложить всё как есть.
UNION — сложить и убрать полные дубликаты.
UNION — это не JOIN
Новички иногда путают UNION и JOIN, потому что оба оператора как будто «объединяют таблицы».
Но делают они совершенно разные вещи.
JOIN соединяет таблицы горизонтально: добавляет колонки рядом.
Например:
SELECT
users.email,
orders.amount
FROM users
JOIN orders ON orders.user_id = users.id;
Результат может быть таким:
email | amount
----------------+--------
anna@mail.com | 1500
bob@mail.com | 2300
Здесь данные пользователя и заказа стоят в одной строке.
А UNION соединяет результаты вертикально: добавляет строки вниз.
Например:
SELECT email
FROM users
UNION ALL
SELECT email
FROM subscribers;
Результат:
email
----------------
anna@mail.com
bob@mail.com
kate@mail.com
tom@mail.com
Можно представить так:
JOIN — приклеить колонки сбоку
UNION — положить строки друг под друга
Это очень важное различие.
Простой пример с двумя списками email
Допустим, у нас есть таблица users:
id | email
---+----------------
1 | anna@mail.com
2 | bob@mail.com
3 | kate@mail.com
И таблица subscribers:
id | email
---+----------------
1 | bob@mail.com
2 | tom@mail.com
3 | kate@mail.com
Мы хотим получить общий список email.
Если нам нужны все строки как есть, используем UNION ALL:
SELECT email
FROM users
UNION ALL
SELECT email
FROM subscribers;
Результат:
email
--------------
anna@mail.com
bob@mail.com
kate@mail.com
bob@mail.com
tom@mail.com
kate@mail.com
bob@mail.com и kate@mail.com повторяются, потому что они есть в обеих таблицах.
Если нам нужен уникальный список email, используем UNION:
SELECT email
FROM users
UNION
SELECT email
FROM subscribers;
Результат:
email
--------------
anna@mail.com
bob@mail.com
kate@mail.com
tom@mail.com
Теперь каждый email встречается только один раз.
Главное отличие UNION и UNION ALL
Разница короткая, но очень важная.
UNION ALL
оставляет все строки.
UNION
удаляет дубликаты.
Например:
SELECT 'SQL' AS course
UNION ALL
SELECT 'SQL' AS course;
Результат:
course
------
SQL
SQL
А теперь:
SELECT 'SQL' AS course
UNION
SELECT 'SQL' AS course;
Результат:
course
------
SQL
В первом случае строки остались обе.
Во втором случае одинаковые строки схлопнулись в одну.
Почему UNION может быть медленнее
UNION ALL работает проще.
База берёт результат первого запроса, потом результат второго запроса и просто складывает их вместе.
А вот UNION должен сделать дополнительную работу: найти и удалить дубликаты.
Для этого базе нужно сравнить строки между собой. На больших объёмах это может означать:
- сортировку результата;
- построение хеш-таблицы;
- дополнительную память;
- временные файлы на диске;
- более долгий план выполнения.
На маленькой таблице разницы можно не заметить.
Но если вы объединяете миллионы строк, лишний UNION может стать серьёзной проблемой.
Поэтому в реальных проектах часто используют правило:
Если дубликаты не мешают или вы точно знаете, что их нет, используйте UNION ALL.
А UNION используйте только тогда, когда вам действительно нужно убрать повторы.
Когда лучше использовать UNION ALL
UNION ALL обычно стоит брать по умолчанию, если нет явной причины удалять дубликаты.
Хорошие случаи для UNION ALL:
- объединить заказы за разные месяцы;
- сложить архивную и текущую таблицу;
- собрать события из разных источников;
- объединить данные из разных шардов;
- подготовить общую витрину, где строки не пересекаются;
- сохранить все факты без потери повторов.
Например, есть таблица заказов за январь:
SELECT id, user_id, amount, created_at
FROM orders_2026_01
И таблица заказов за февраль:
SELECT id, user_id, amount, created_at
FROM orders_2026_02
Если один и тот же заказ не может одновременно лежать в январской и февральской таблице, нет смысла использовать UNION.
Лучше так:
SELECT id, user_id, amount, created_at
FROM orders_2026_01
UNION ALL
SELECT id, user_id, amount, created_at
FROM orders_2026_02;
Такой запрос просто сложит строки без лишней проверки на дубликаты.
Когда нужен UNION
UNION нужен, когда дубликаты действительно возможны и их нужно убрать.
Например, мы собираем общий список email из нескольких источников:
SELECT email
FROM users
UNION
SELECT email
FROM subscribers
UNION
SELECT email
FROM leads;
Если один и тот же email есть в нескольких таблицах, в итоговом списке он останется один раз.
Это полезно для задач вроде:
- получить уникальный список клиентов;
- собрать уникальные страны;
- объединить справочники;
- убрать повторы после нескольких источников;
- подготовить список для рассылки без дублей.
Но важно понимать: UNION удаляет только полностью одинаковые строки.
Что считается дубликатом в UNION
UNION сравнивает всю строку целиком, а не только первую колонку.
Например:
SELECT 'anna@mail.com' AS email, 'user' AS source
UNION
SELECT 'anna@mail.com' AS email, 'lead' AS source;
Результат:
email | source
---------------+-------
anna@mail.com | lead
anna@mail.com | user
Почему email один и тот же, но строки не схлопнулись?
Потому что вторая колонка отличается:
user
lead
Для UNION это разные строки.
А вот такой запрос:
SELECT 'anna@mail.com' AS email, 'user' AS source
UNION
SELECT 'anna@mail.com' AS email, 'user' AS source;
вернёт одну строку:
email | source
---------------+-------
anna@mail.com | user
Потому что совпали все колонки.
Главное правило:
UNION удаляет дубликаты по всей строке целиком.
Как UNION работает с NULL
В обычных сравнениях SQL NULL ведёт себя непривычно: NULL = NULL не даёт TRUE.
Но при удалении дублей в UNION две строки с NULL в одинаковых местах считаются одинаковыми.
Например:
SELECT NULL AS country
UNION
SELECT NULL AS country;
Результат:
country
-------
NULL
Останется одна строка.
А с UNION ALL будет две:
SELECT NULL AS country
UNION ALL
SELECT NULL AS country;
Результат:
country
-------
NULL
NULL
Для новичка это может выглядеть странно, но в контексте дедупликации UNION считает такие строки дубликатами.
Требования к количеству колонок
У всех SELECT внутри UNION должно быть одинаковое количество колонок.
Такой запрос корректный:
SELECT id, email
FROM users
UNION ALL
SELECT id, email
FROM subscribers;
В обоих запросах по две колонки.
А такой запрос упадёт:
SELECT id, email
FROM users
UNION ALL
SELECT id, user_id, amount
FROM orders;
Почему?
Потому что в первом SELECT две колонки:
id, email
А во втором три:
id, user_id, amount
База не понимает, как складывать такие строки друг под друга.
Это как пытаться объединить две таблицы такого вида:
id | email
и:
id | user_id | amount
Структура строк должна совпадать.
Порядок колонок важен
В UNION важны не только количество колонок, но и их порядок.
Например:
SELECT id, email
FROM users
UNION ALL
SELECT user_id, status
FROM orders;
Технически здесь две колонки и две колонки. Запрос может выполниться, если типы совместимы.
Но по смыслу результат будет странный:
id | email
---+--------
1 | anna@mail.com
2 | bob@mail.com
1 | paid
2 | failed
Вторая колонка сначала содержит email, а потом статус заказа. Формально это может быть текст, но логически это разные сущности.
Поэтому при UNION важно не просто сделать одинаковое количество колонок, а убедиться, что колонки совпадают по смыслу.
Хороший результат должен быть похож на одну общую таблицу.
Типы колонок должны быть совместимы
Колонки на одинаковых позициях должны иметь совместимые типы.
Например:
SELECT id, email
FROM users
UNION ALL
SELECT id, status
FROM orders;
Если email и status оба текстовые, база может объединить их.
А вот если в одной ветке на второй позиции текст, а в другой — дата или число, может потребоваться явное приведение типа.
Например:
SELECT id, email::text AS value
FROM users
UNION ALL
SELECT id, amount::text AS value
FROM orders;
Здесь мы явно приводим email и amount к тексту, чтобы получить общий формат.
Но нужно быть аккуратным: если приходится приводить типы, спросите себя, действительно ли эти данные должны лежать в одной колонке.
Иногда правильнее добавить дополнительные колонки и сделать результат понятнее.
Имена колонок берутся из первого SELECT
Ещё одна важная особенность: названия колонок в итоговом результате берутся из первого SELECT.
Например:
SELECT id, email
FROM users
UNION ALL
SELECT user_id, status
FROM orders;
Итоговые колонки будут называться так, как в первом запросе:
id | email
Даже если во втором запросе они назывались user_id и status.
Поэтому первый SELECT лучше оформлять аккуратно и давать колонкам понятные алиасы.
Например:
SELECT
id,
email AS value,
'user' AS source
FROM users
UNION ALL
SELECT
id,
status AS value,
'order' AS source
FROM orders;
Результат:
id | value | source
---+----------------+--------
1 | anna@mail.com | user
2 | paid | order
Теперь видно, откуда пришла строка и что лежит в колонке value.
Зачем добавлять колонку source
В реальных задачах при объединении данных часто полезно добавить техническую колонку с источником.
Например:
SELECT
id,
email,
'users' AS source
FROM users
UNION ALL
SELECT
id,
email,
'subscribers' AS source
FROM subscribers;
Результат:
id | email | source
---+----------------+------------
1 | anna@mail.com | users
2 | bob@mail.com | users
1 | bob@mail.com | subscribers
2 | tom@mail.com | subscribers
Колонка source помогает понять, из какой таблицы пришла строка.
Это особенно полезно при:
- отладке отчётов;
- объединении нескольких источников;
- миграциях;
- сравнении старой и новой системы;
- построении витрин данных.
Без такой колонки можно быстро потерять контекст.
Как объединить разные сущности в одну таблицу
Иногда нужно собрать общий список событий, хотя данные лежат в разных таблицах.
Например, есть регистрации пользователей и заказы.
Таблица users:
id | email | created_at
---+----------------+---------------------
1 | anna@mail.com | 2026-06-01 10:00:00
Таблица orders:
id | user_id | amount | created_at
---+---------+--------+---------------------
10 | 1 | 1500 | 2026-06-02 12:00:00
Хотим получить одну ленту событий:
event_time | event_type | description
---------------------+------------+---------------------
2026-06-01 10:00:00 | signup | anna@mail.com
2026-06-02 12:00:00 | order | 1500
Запрос:
SELECT
created_at AS event_time,
'signup' AS event_type,
email AS description
FROM users
UNION ALL
SELECT
created_at AS event_time,
'order' AS event_type,
amount::text AS description
FROM orders
ORDER BY event_time;
Здесь мы специально приводим amount к тексту:
amount::text
потому что в колонке description в первой ветке лежит email, а во второй — сумма заказа.
Так мы строим единую ленту из разных типов событий.
Как добавить NULL для отсутствующих колонок
Бывает, что у одной сущности есть колонка, а у другой её нет.
Например, у заказов есть amount, а у пользователей — нет.
Но для UNION количество колонок должно совпадать. Тогда можно добавить NULL в той ветке, где значения нет.
SELECT
id,
email,
NULL::numeric AS amount,
'user' AS source
FROM users
UNION ALL
SELECT
id,
NULL::text AS email,
amount,
'order' AS source
FROM orders;
Результат:
id | email | amount | source
---+----------------+--------+--------
1 | anna@mail.com | NULL | user
10 | NULL | 1500 | order
Зачем писать NULL::numeric и NULL::text?
Чтобы явно подсказать PostgreSQL тип колонки.
Обычный NULL сам по себе не имеет конкретного типа. Когда запрос сложный, явное приведение помогает избежать неоднозначности и делает структуру результата понятнее.
ORDER BY при UNION
Если нужно отсортировать итоговый результат, ORDER BY пишется один раз — в самом конце.
Например:
SELECT id, name, salary
FROM employees
WHERE dept = 'eng'
UNION ALL
SELECT id, name, salary
FROM employees
WHERE dept = 'sales'
ORDER BY salary DESC;
Здесь сортируется весь объединённый результат: и сотрудники из eng, и сотрудники из sales.
Важно: финальный ORDER BY относится ко всему результату после UNION.
Не к первой ветке.
Не ко второй ветке.
А ко всему набору строк.
LIMIT при UNION
LIMIT тоже обычно ставится в конце, если нужно ограничить весь итоговый результат.
Например:
SELECT id, name, salary
FROM employees
WHERE dept = 'eng'
UNION ALL
SELECT id, name, salary
FROM employees
WHERE dept = 'sales'
ORDER BY salary DESC
LIMIT 10;
Такой запрос вернёт топ-10 сотрудников по зарплате среди двух отделов вместе.
То есть сначала объединяем сотрудников из eng и sales, потом сортируем по зарплате, потом берём первые 10 строк.
Как ограничить только одну ветку
Иногда нужно применить ORDER BY и LIMIT не ко всему результату, а только к одному SELECT.
Например, взять топ-5 оплаченных заказов и добавить к ним все возвраты.
Тогда ветку нужно обернуть в подзапрос:
SELECT *
FROM (
SELECT id, amount, status
FROM orders
WHERE status = 'paid'
ORDER BY amount DESC
LIMIT 5
) AS top_paid
UNION ALL
SELECT id, amount, status
FROM orders
WHERE status = 'refunded';
Здесь ORDER BY amount DESC LIMIT 5 применяется только к первой части.
Без подзапроса можно случайно получить совсем другой смысл или ошибку синтаксиса в зависимости от СУБД.
Практическое правило:
Если сортировка или лимит нужны для отдельной ветки UNION, заверните эту ветку в подзапрос.
ORDER BY по номеру колонки
В итоговом ORDER BY можно ссылаться на колонку по имени или по номеру.
Например:
SELECT id, name, salary
FROM employees
WHERE dept = 'eng'
UNION ALL
SELECT id, name, salary
FROM employees
WHERE dept = 'sales'
ORDER BY 3 DESC;
ORDER BY 3 означает: сортировать по третьей колонке.
В нашем случае третья колонка — это salary.
Но для новичков лучше писать явно:
ORDER BY salary DESC
Так запрос проще читать.
Номера колонок иногда удобны в быстрых аналитических запросах, но в учебном и продуктовом коде понятные имена обычно лучше.
Скобки и порядок выполнения
UNION и UNION ALL можно комбинировать.
Например:
SELECT email FROM users
UNION ALL
SELECT email FROM subscribers
UNION
SELECT email FROM leads;
Но такие запросы уже сложнее читать. Особенно если часть данных нужно оставить с дублями, а часть — дедуплицировать.
Чтобы явно показать порядок, можно использовать скобки.
Например:
(
SELECT email FROM users
UNION ALL
SELECT email FROM subscribers
)
UNION
SELECT email FROM leads;
Смысл такой:
- сначала объединяем
users и subscribers через UNION ALL;
- потом объединяем результат с
leads через UNION, удаляя дубликаты.
Если логика сложная, лучше не писать всё одной длинной цепочкой. Используйте скобки или CTE, чтобы следующий человек понял ваш замысел.
UNION ALL и дальнейшая дедупликация
Иногда удобнее сначала собрать всё через UNION ALL, а потом отдельно решить, как именно удалять дубликаты.
Например:
WITH all_emails AS (
SELECT email, 'users' AS source FROM users
UNION ALL
SELECT email, 'subscribers' AS source FROM subscribers
UNION ALL
SELECT email, 'leads' AS source FROM leads
)
SELECT DISTINCT email
FROM all_emails;
Такой вариант читается явно:
all_emails — собрали все email из всех источников;
- потом выбрали уникальные email.
Это особенно удобно, если нужна не просто дедупликация всей строки, а более тонкая логика.
Например, оставить по одному email, но при этом выбрать приоритетный источник.
Частая ошибка: использовать UNION вместо UNION ALL «на всякий случай»
Многие новички ставят UNION, потому что он кажется более «аккуратным»: вдруг будут дубли?
Но это не всегда хорошо.
Например, вы объединяете события из двух независимых источников:
SELECT user_id, event_name, created_at
FROM web_events
UNION
SELECT user_id, event_name, created_at
FROM mobile_events;
Если одинаковое событие пришло из web и mobile, UNION может схлопнуть строки, хотя в аналитике это могут быть два разных факта.
В результате вы потеряете данные.
Если вам нужны все события, правильнее:
SELECT user_id, event_name, created_at
FROM web_events
UNION ALL
SELECT user_id, event_name, created_at
FROM mobile_events;
А если нужно различать источники, добавьте колонку:
SELECT user_id, event_name, created_at, 'web' AS source
FROM web_events
UNION ALL
SELECT user_id, event_name, created_at, 'mobile' AS source
FROM mobile_events;
Теперь даже одинаковые события из разных источников не потеряются, потому что источник явно сохранён.
Частая ошибка: думать, что UNION удаляет дубли по одной колонке
Допустим, есть запрос:
SELECT email, 'users' AS source
FROM users
UNION
SELECT email, 'leads' AS source
FROM leads;
Если один и тот же email есть в обеих таблицах, он всё равно появится два раза:
email | source
---------------+-------
anna@mail.com | users
anna@mail.com | leads
Почему?
Потому что строки отличаются по колонке source.
Если вам нужен уникальный список только по email, нужно выбирать только email:
SELECT email
FROM users
UNION
SELECT email
FROM leads;
Или собрать всё через UNION ALL, а потом сделать отдельную дедупликацию по email.
Например:
WITH all_emails AS (
SELECT email, 'users' AS source FROM users
UNION ALL
SELECT email, 'leads' AS source FROM leads
)
SELECT DISTINCT email
FROM all_emails;
Главная мысль:
UNION удаляет дубли по всем выбранным колонкам, а не по одной выбранной вами колонке в голове.
Пример: архивная и текущая таблица
Очень частый рабочий сценарий — есть текущая таблица и архив.
Например:
orders_current
orders_archive
У них одинаковая структура:
id | user_id | amount | status | created_at
Чтобы получить все заказы, можно написать:
SELECT id, user_id, amount, status, created_at
FROM orders_current
UNION ALL
SELECT id, user_id, amount, status, created_at
FROM orders_archive;
Почему здесь обычно UNION ALL?
Потому что архив и текущая таблица, как правило, не должны пересекаться. Если данные разложены корректно, один и тот же заказ не лежит одновременно и там, и там.
Если поставить UNION, база будет тратить силы на проверку дублей, которые по бизнес-логике невозможны.
Пример: отчёт по нескольким источникам
Допустим, у нас есть заказы из сайта и мобильного приложения.
SELECT
user_id,
amount,
created_at,
'web' AS source
FROM web_orders
UNION ALL
SELECT
user_id,
amount,
created_at,
'mobile' AS source
FROM mobile_orders;
Так мы получаем общий поток заказов.
Потом его можно агрегировать:
WITH all_orders AS (
SELECT user_id, amount, created_at, 'web' AS source
FROM web_orders
UNION ALL
SELECT user_id, amount, created_at, 'mobile' AS source
FROM mobile_orders
)
SELECT
source,
COUNT(*) AS orders_count,
SUM(amount) AS revenue
FROM all_orders
GROUP BY source;
Результат:
source | orders_count | revenue
-------+--------------+---------
web | 1500 | 2300000
mobile | 2100 | 3400000
Здесь UNION ALL особенно важен: мы считаем факты заказов, а не уникальные строки.
Как проверить, нужны ли дубли
Если вы сомневаетесь, можно сначала посмотреть, есть ли пересечения.
Например, есть две таблицы email:
SELECT email FROM users
UNION ALL
SELECT email FROM leads;
Чтобы найти повторяющиеся email после объединения:
WITH all_emails AS (
SELECT email FROM users
UNION ALL
SELECT email FROM leads
)
SELECT
email,
COUNT(*) AS cnt
FROM all_emails
GROUP BY email
HAVING COUNT(*) > 1;
Так вы увидите, какие значения встречаются больше одного раза.
После этого уже можно осознанно решить:
- оставить дубли, потому что это разные факты;
- убрать дубли, потому что нужен уникальный список;
- добавить колонку
source;
- дедуплицировать по специальным правилам.
UNION, UNION ALL и DISTINCT
По смыслу:
UNION
похож на:
UNION ALL + DISTINCT
То есть такой запрос:
SELECT email FROM users
UNION
SELECT email FROM leads;
примерно похож по идее на:
SELECT DISTINCT email
FROM (
SELECT email FROM users
UNION ALL
SELECT email FROM leads
) AS all_emails;
Но второй вариант иногда удобнее читать и расширять, особенно если вы хотите сначала собрать данные, а потом отдельно управлять дедупликацией.
Например, можно добавить source, дату загрузки, приоритет и уже потом выбрать нужные строки.
Отличия в PostgreSQL
В PostgreSQL UNION и UNION ALL работают строго и предсказуемо.
PostgreSQL проверяет:
- одинаковое количество колонок;
- совместимость типов;
- корректность итогового
ORDER BY.
При UNION PostgreSQL должен удалить дубликаты. В плане запроса это может выглядеть как сортировка или хеширование.
Чтобы посмотреть, что реально делает база, можно использовать:
EXPLAIN
SELECT country FROM users
UNION
SELECT country FROM employees;
Или с фактическим выполнением:
EXPLAIN ANALYZE
SELECT country FROM users
UNION
SELECT country FROM employees;
Если таблицы большие, полезно сравнить план для UNION и UNION ALL.
Часто вы увидите, что UNION ALL проще и дешевле.
Отличия в MySQL
В MySQL идея такая же:
SELECT email FROM users
UNION
SELECT email FROM leads;
удаляет дубликаты.
А:
SELECT email FROM users
UNION ALL
SELECT email FROM leads;
оставляет все строки.
Но MySQL может быть мягче в неявных приведениях типов. Например, если в одной ветке число, а в другой текст, он может попытаться привести значения сам.
Иногда это удобно, но иногда приводит к неожиданным результатам. Поэтому для понятного запроса лучше явно выравнивать типы через CAST.
Ещё один практический момент: если в MySQL нужно применить ORDER BY или LIMIT к отдельной ветке, используйте подзапрос.
Например:
SELECT *
FROM (
SELECT id, amount
FROM orders
WHERE status = 'paid'
ORDER BY amount DESC
LIMIT 5
) AS top_paid
UNION ALL
SELECT id, amount
FROM orders
WHERE status = 'refunded';
Так смысл будет явным.
Отличия в ClickHouse
В ClickHouse UNION ALL встречается особенно часто, потому что ClickHouse часто используют для аналитики и больших потоков данных.
Пример:
SELECT user_id, event_name, created_at
FROM web_events
UNION ALL
SELECT user_id, event_name, created_at
FROM mobile_events;
Для аналитических событий обычно важно не потерять строки, поэтому UNION ALL часто оказывается правильным выбором.
Если нужно убрать дубликаты, в ClickHouse обычно явно используют DISTINCT или агрегацию после объединения.
Например:
SELECT DISTINCT email
FROM (
SELECT email FROM users
UNION ALL
SELECT email FROM leads
);
Общее правило остаётся тем же:
если нужны все строки — UNION ALL;
если нужен уникальный результат — добавляйте дедупликацию осознанно.
Практические шаблоны
Объединить две таблицы без удаления дублей
SELECT email
FROM users
UNION ALL
SELECT email
FROM subscribers;
Получить уникальный список email
SELECT email
FROM users
UNION
SELECT email
FROM subscribers;
Добавить источник строки
SELECT id, email, 'users' AS source
FROM users
UNION ALL
SELECT id, email, 'subscribers' AS source
FROM subscribers;
Объединить текущие и архивные заказы
SELECT id, user_id, amount, status, created_at
FROM orders_current
UNION ALL
SELECT id, user_id, amount, status, created_at
FROM orders_archive;
Сделать общую ленту событий
SELECT created_at AS event_time, 'signup' AS event_type, email AS description
FROM users
UNION ALL
SELECT created_at AS event_time, 'order' AS event_type, amount::text AS description
FROM orders
ORDER BY event_time;
Отсортировать общий результат
SELECT id, name, salary
FROM employees
WHERE dept = 'eng'
UNION ALL
SELECT id, name, salary
FROM employees
WHERE dept = 'sales'
ORDER BY salary DESC;
Ограничить весь объединённый результат
SELECT id, name, salary
FROM employees
WHERE dept = 'eng'
UNION ALL
SELECT id, name, salary
FROM employees
WHERE dept = 'sales'
ORDER BY salary DESC
LIMIT 10;
Ограничить только одну ветку
SELECT *
FROM (
SELECT id, amount
FROM orders
WHERE status = 'paid'
ORDER BY amount DESC
LIMIT 5
) AS top_paid
UNION ALL
SELECT id, amount
FROM orders
WHERE status = 'refunded';
Что важно запомнить
UNION и UNION ALL объединяют результаты запросов вертикально.
То есть строки второго SELECT добавляются под строки первого SELECT.
Главная разница:
UNION ALL — оставляет все строки
UNION — удаляет дубликаты
UNION ALL обычно быстрее, потому что базе не нужно искать повторы.
UNION может быть медленнее, потому что для удаления дублей базе нужно сравнить строки между собой.
У всех SELECT внутри объединения должно быть:
- одинаковое количество колонок;
- совместимые типы колонок;
- одинаковый смысл колонок на одинаковых позициях.
Имена итоговых колонок берутся из первого SELECT.
Если нужно понять, откуда пришла строка, добавляйте колонку-источник:
SELECT email, 'users' AS source FROM users
UNION ALL
SELECT email, 'leads' AS source FROM leads;
Если нужен общий порядок, ORDER BY пишется в самом конце.
Короткий вывод
UNION и UNION ALL нужны, когда вы хотите сложить результаты нескольких SELECT друг под друга.
В большинстве рабочих задач стоит начинать с UNION ALL.
Он проще, быстрее и не удаляет строки без вашего явного решения.
SELECT email FROM users
UNION ALL
SELECT email FROM leads;
А UNION используйте тогда, когда вам действительно нужен уникальный результат:
SELECT email FROM users
UNION
SELECT email FROM leads;
Главная мысль:
UNION ALL сохраняет факты.
UNION делает уникальный список.
Если вы объединяете события, заказы, логи или архивы — чаще всего нужен UNION ALL.
Если вы собираете список уникальных email, стран, кодов или справочных значений — может подойти UNION.
Хороший SQL-разработчик выбирает между ними не наугад, а по смыслу данных: нужно ли сохранять все строки или действительно нужно убрать дубликаты.
В SQL есть два популярных способа объединять данные из разных запросов:
UNIONи:
UNION ALLНа первый взгляд они почти одинаковые. Оба позволяют взять результат одного
SELECTи добавить под него результат другогоSELECT.То есть строки не соединяются рядом, как в
JOIN, а складываются вертикально — одна выборка под другой.Например, у нас есть пользователи из одной таблицы и клиенты из другой. Мы хотим получить один общий список email. В такой ситуации как раз может пригодиться
UNIONилиUNION ALL.Но между ними есть важная разница:
UNION ALLоставляет все строки, включая дубликаты;UNIONубирает повторяющиеся строки.Эта разница влияет не только на результат, но и на скорость запроса. Иногда
UNIONделает полезную работу, а иногда просто тратит ресурсы на ненужную дедупликацию.Разберёмся спокойно и на примерах.
Что делает UNION простыми словами
Представим два запроса.
Первый возвращает страны пользователей:
SELECT country FROM users;Результат:
Второй возвращает страны сотрудников:
SELECT country FROM employees;Результат:
Если мы хотим получить один общий список стран, можно объединить результаты:
SELECT country FROM users UNION SELECT country FROM employees;Результат будет таким:
Обратите внимание:
VietnamиGermanyбыли в обеих выборках, но в итоговом результате остались по одному разу.Так работает
UNION: он объединяет строки и убирает дубликаты.Что делает UNION ALL
Теперь возьмём тот же пример, но используем
UNION ALL.SELECT country FROM users UNION ALL SELECT country FROM employees;Результат будет другим:
Здесь все строки остались на месте.
Если
Germanyбыла в первой выборке и во второй, она появится два раза. ЕслиVietnamбыла в первой выборке и во второй, она тоже появится два раза.UNION ALLничего не удаляет. Он просто берёт строки из первого запроса и дописывает под них строки из второго.Можно запомнить так:
UNION — это не JOIN
Новички иногда путают
UNIONиJOIN, потому что оба оператора как будто «объединяют таблицы».Но делают они совершенно разные вещи.
JOINсоединяет таблицы горизонтально: добавляет колонки рядом.Например:
SELECT users.email, orders.amount FROM users JOIN orders ON orders.user_id = users.id;Результат может быть таким:
Здесь данные пользователя и заказа стоят в одной строке.
А
UNIONсоединяет результаты вертикально: добавляет строки вниз.Например:
SELECT email FROM users UNION ALL SELECT email FROM subscribers;Результат:
Можно представить так:
Это очень важное различие.
Простой пример с двумя списками email
Допустим, у нас есть таблица
users:И таблица
subscribers:Мы хотим получить общий список email.
Если нам нужны все строки как есть, используем
UNION ALL:SELECT email FROM users UNION ALL SELECT email FROM subscribers;Результат:
bob@mail.comиkate@mail.comповторяются, потому что они есть в обеих таблицах.Если нам нужен уникальный список email, используем
UNION:SELECT email FROM users UNION SELECT email FROM subscribers;Результат:
Теперь каждый email встречается только один раз.
Главное отличие UNION и UNION ALL
Разница короткая, но очень важная.
UNION ALLоставляет все строки.
UNIONудаляет дубликаты.
Например:
SELECT 'SQL' AS course UNION ALL SELECT 'SQL' AS course;Результат:
А теперь:
SELECT 'SQL' AS course UNION SELECT 'SQL' AS course;Результат:
В первом случае строки остались обе. Во втором случае одинаковые строки схлопнулись в одну.
Почему UNION может быть медленнее
UNION ALLработает проще.База берёт результат первого запроса, потом результат второго запроса и просто складывает их вместе.
А вот
UNIONдолжен сделать дополнительную работу: найти и удалить дубликаты.Для этого базе нужно сравнить строки между собой. На больших объёмах это может означать:
На маленькой таблице разницы можно не заметить.
Но если вы объединяете миллионы строк, лишний
UNIONможет стать серьёзной проблемой.Поэтому в реальных проектах часто используют правило:
А
UNIONиспользуйте только тогда, когда вам действительно нужно убрать повторы.Когда лучше использовать UNION ALL
UNION ALLобычно стоит брать по умолчанию, если нет явной причины удалять дубликаты.Хорошие случаи для
UNION ALL:Например, есть таблица заказов за январь:
SELECT id, user_id, amount, created_at FROM orders_2026_01И таблица заказов за февраль:
SELECT id, user_id, amount, created_at FROM orders_2026_02Если один и тот же заказ не может одновременно лежать в январской и февральской таблице, нет смысла использовать
UNION.Лучше так:
SELECT id, user_id, amount, created_at FROM orders_2026_01 UNION ALL SELECT id, user_id, amount, created_at FROM orders_2026_02;Такой запрос просто сложит строки без лишней проверки на дубликаты.
Когда нужен UNION
UNIONнужен, когда дубликаты действительно возможны и их нужно убрать.Например, мы собираем общий список email из нескольких источников:
SELECT email FROM users UNION SELECT email FROM subscribers UNION SELECT email FROM leads;Если один и тот же email есть в нескольких таблицах, в итоговом списке он останется один раз.
Это полезно для задач вроде:
Но важно понимать:
UNIONудаляет только полностью одинаковые строки.Что считается дубликатом в UNION
UNIONсравнивает всю строку целиком, а не только первую колонку.Например:
SELECT 'anna@mail.com' AS email, 'user' AS source UNION SELECT 'anna@mail.com' AS email, 'lead' AS source;Результат:
Почему email один и тот же, но строки не схлопнулись?
Потому что вторая колонка отличается:
Для
UNIONэто разные строки.А вот такой запрос:
SELECT 'anna@mail.com' AS email, 'user' AS source UNION SELECT 'anna@mail.com' AS email, 'user' AS source;вернёт одну строку:
Потому что совпали все колонки.
Главное правило:
Как UNION работает с NULL
В обычных сравнениях SQL
NULLведёт себя непривычно:NULL = NULLне даётTRUE.Но при удалении дублей в
UNIONдве строки сNULLв одинаковых местах считаются одинаковыми.Например:
SELECT NULL AS country UNION SELECT NULL AS country;Результат:
Останется одна строка.
А с
UNION ALLбудет две:SELECT NULL AS country UNION ALL SELECT NULL AS country;Результат:
Для новичка это может выглядеть странно, но в контексте дедупликации
UNIONсчитает такие строки дубликатами.Требования к количеству колонок
У всех
SELECTвнутриUNIONдолжно быть одинаковое количество колонок.Такой запрос корректный:
SELECT id, email FROM users UNION ALL SELECT id, email FROM subscribers;В обоих запросах по две колонки.
А такой запрос упадёт:
SELECT id, email FROM users UNION ALL SELECT id, user_id, amount FROM orders;Почему?
Потому что в первом
SELECTдве колонки:А во втором три:
База не понимает, как складывать такие строки друг под друга.
Это как пытаться объединить две таблицы такого вида:
и:
Структура строк должна совпадать.
Порядок колонок важен
В
UNIONважны не только количество колонок, но и их порядок.Например:
SELECT id, email FROM users UNION ALL SELECT user_id, status FROM orders;Технически здесь две колонки и две колонки. Запрос может выполниться, если типы совместимы.
Но по смыслу результат будет странный:
Вторая колонка сначала содержит email, а потом статус заказа. Формально это может быть текст, но логически это разные сущности.
Поэтому при
UNIONважно не просто сделать одинаковое количество колонок, а убедиться, что колонки совпадают по смыслу.Хороший результат должен быть похож на одну общую таблицу.
Типы колонок должны быть совместимы
Колонки на одинаковых позициях должны иметь совместимые типы.
Например:
SELECT id, email FROM users UNION ALL SELECT id, status FROM orders;Если
emailиstatusоба текстовые, база может объединить их.А вот если в одной ветке на второй позиции текст, а в другой — дата или число, может потребоваться явное приведение типа.
Например:
SELECT id, email::text AS value FROM users UNION ALL SELECT id, amount::text AS value FROM orders;Здесь мы явно приводим
emailиamountк тексту, чтобы получить общий формат.Но нужно быть аккуратным: если приходится приводить типы, спросите себя, действительно ли эти данные должны лежать в одной колонке.
Иногда правильнее добавить дополнительные колонки и сделать результат понятнее.
Имена колонок берутся из первого SELECT
Ещё одна важная особенность: названия колонок в итоговом результате берутся из первого
SELECT.Например:
SELECT id, email FROM users UNION ALL SELECT user_id, status FROM orders;Итоговые колонки будут называться так, как в первом запросе:
Даже если во втором запросе они назывались
user_idиstatus.Поэтому первый
SELECTлучше оформлять аккуратно и давать колонкам понятные алиасы.Например:
SELECT id, email AS value, 'user' AS source FROM users UNION ALL SELECT id, status AS value, 'order' AS source FROM orders;Результат:
Теперь видно, откуда пришла строка и что лежит в колонке
value.Зачем добавлять колонку source
В реальных задачах при объединении данных часто полезно добавить техническую колонку с источником.
Например:
SELECT id, email, 'users' AS source FROM users UNION ALL SELECT id, email, 'subscribers' AS source FROM subscribers;Результат:
Колонка
sourceпомогает понять, из какой таблицы пришла строка.Это особенно полезно при:
Без такой колонки можно быстро потерять контекст.
Как объединить разные сущности в одну таблицу
Иногда нужно собрать общий список событий, хотя данные лежат в разных таблицах.
Например, есть регистрации пользователей и заказы.
Таблица
users:Таблица
orders:Хотим получить одну ленту событий:
Запрос:
SELECT created_at AS event_time, 'signup' AS event_type, email AS description FROM users UNION ALL SELECT created_at AS event_time, 'order' AS event_type, amount::text AS description FROM orders ORDER BY event_time;Здесь мы специально приводим
amountк тексту:потому что в колонке
descriptionв первой ветке лежит email, а во второй — сумма заказа.Так мы строим единую ленту из разных типов событий.
Как добавить NULL для отсутствующих колонок
Бывает, что у одной сущности есть колонка, а у другой её нет.
Например, у заказов есть
amount, а у пользователей — нет.Но для
UNIONколичество колонок должно совпадать. Тогда можно добавитьNULLв той ветке, где значения нет.SELECT id, email, NULL::numeric AS amount, 'user' AS source FROM users UNION ALL SELECT id, NULL::text AS email, amount, 'order' AS source FROM orders;Результат:
Зачем писать
NULL::numericиNULL::text?Чтобы явно подсказать PostgreSQL тип колонки.
Обычный
NULLсам по себе не имеет конкретного типа. Когда запрос сложный, явное приведение помогает избежать неоднозначности и делает структуру результата понятнее.ORDER BY при UNION
Если нужно отсортировать итоговый результат,
ORDER BYпишется один раз — в самом конце.Например:
SELECT id, name, salary FROM employees WHERE dept = 'eng' UNION ALL SELECT id, name, salary FROM employees WHERE dept = 'sales' ORDER BY salary DESC;Здесь сортируется весь объединённый результат: и сотрудники из
eng, и сотрудники изsales.Важно: финальный
ORDER BYотносится ко всему результату послеUNION.Не к первой ветке. Не ко второй ветке. А ко всему набору строк.
LIMIT при UNION
LIMITтоже обычно ставится в конце, если нужно ограничить весь итоговый результат.Например:
SELECT id, name, salary FROM employees WHERE dept = 'eng' UNION ALL SELECT id, name, salary FROM employees WHERE dept = 'sales' ORDER BY salary DESC LIMIT 10;Такой запрос вернёт топ-10 сотрудников по зарплате среди двух отделов вместе.
То есть сначала объединяем сотрудников из
engиsales, потом сортируем по зарплате, потом берём первые 10 строк.Как ограничить только одну ветку
Иногда нужно применить
ORDER BYиLIMITне ко всему результату, а только к одномуSELECT.Например, взять топ-5 оплаченных заказов и добавить к ним все возвраты.
Тогда ветку нужно обернуть в подзапрос:
SELECT * FROM ( SELECT id, amount, status FROM orders WHERE status = 'paid' ORDER BY amount DESC LIMIT 5 ) AS top_paid UNION ALL SELECT id, amount, status FROM orders WHERE status = 'refunded';Здесь
ORDER BY amount DESC LIMIT 5применяется только к первой части.Без подзапроса можно случайно получить совсем другой смысл или ошибку синтаксиса в зависимости от СУБД.
Практическое правило:
ORDER BY по номеру колонки
В итоговом
ORDER BYможно ссылаться на колонку по имени или по номеру.Например:
SELECT id, name, salary FROM employees WHERE dept = 'eng' UNION ALL SELECT id, name, salary FROM employees WHERE dept = 'sales' ORDER BY 3 DESC;ORDER BY 3означает: сортировать по третьей колонке.В нашем случае третья колонка — это
salary.Но для новичков лучше писать явно:
ORDER BY salary DESCТак запрос проще читать.
Номера колонок иногда удобны в быстрых аналитических запросах, но в учебном и продуктовом коде понятные имена обычно лучше.
Скобки и порядок выполнения
UNIONиUNION ALLможно комбинировать.Например:
SELECT email FROM users UNION ALL SELECT email FROM subscribers UNION SELECT email FROM leads;Но такие запросы уже сложнее читать. Особенно если часть данных нужно оставить с дублями, а часть — дедуплицировать.
Чтобы явно показать порядок, можно использовать скобки.
Например:
( SELECT email FROM users UNION ALL SELECT email FROM subscribers ) UNION SELECT email FROM leads;Смысл такой:
usersиsubscribersчерезUNION ALL;leadsчерезUNION, удаляя дубликаты.Если логика сложная, лучше не писать всё одной длинной цепочкой. Используйте скобки или CTE, чтобы следующий человек понял ваш замысел.
UNION ALL и дальнейшая дедупликация
Иногда удобнее сначала собрать всё через
UNION ALL, а потом отдельно решить, как именно удалять дубликаты.Например:
WITH all_emails AS ( SELECT email, 'users' AS source FROM users UNION ALL SELECT email, 'subscribers' AS source FROM subscribers UNION ALL SELECT email, 'leads' AS source FROM leads ) SELECT DISTINCT email FROM all_emails;Такой вариант читается явно:
all_emails— собрали все email из всех источников;Это особенно удобно, если нужна не просто дедупликация всей строки, а более тонкая логика.
Например, оставить по одному email, но при этом выбрать приоритетный источник.
Частая ошибка: использовать UNION вместо UNION ALL «на всякий случай»
Многие новички ставят
UNION, потому что он кажется более «аккуратным»: вдруг будут дубли?Но это не всегда хорошо.
Например, вы объединяете события из двух независимых источников:
SELECT user_id, event_name, created_at FROM web_events UNION SELECT user_id, event_name, created_at FROM mobile_events;Если одинаковое событие пришло из web и mobile,
UNIONможет схлопнуть строки, хотя в аналитике это могут быть два разных факта.В результате вы потеряете данные.
Если вам нужны все события, правильнее:
SELECT user_id, event_name, created_at FROM web_events UNION ALL SELECT user_id, event_name, created_at FROM mobile_events;А если нужно различать источники, добавьте колонку:
SELECT user_id, event_name, created_at, 'web' AS source FROM web_events UNION ALL SELECT user_id, event_name, created_at, 'mobile' AS source FROM mobile_events;Теперь даже одинаковые события из разных источников не потеряются, потому что источник явно сохранён.
Частая ошибка: думать, что UNION удаляет дубли по одной колонке
Допустим, есть запрос:
SELECT email, 'users' AS source FROM users UNION SELECT email, 'leads' AS source FROM leads;Если один и тот же email есть в обеих таблицах, он всё равно появится два раза:
Почему?
Потому что строки отличаются по колонке
source.Если вам нужен уникальный список только по email, нужно выбирать только email:
SELECT email FROM users UNION SELECT email FROM leads;Или собрать всё через
UNION ALL, а потом сделать отдельную дедупликацию по email.Например:
WITH all_emails AS ( SELECT email, 'users' AS source FROM users UNION ALL SELECT email, 'leads' AS source FROM leads ) SELECT DISTINCT email FROM all_emails;Главная мысль:
Пример: архивная и текущая таблица
Очень частый рабочий сценарий — есть текущая таблица и архив.
Например:
У них одинаковая структура:
Чтобы получить все заказы, можно написать:
SELECT id, user_id, amount, status, created_at FROM orders_current UNION ALL SELECT id, user_id, amount, status, created_at FROM orders_archive;Почему здесь обычно
UNION ALL?Потому что архив и текущая таблица, как правило, не должны пересекаться. Если данные разложены корректно, один и тот же заказ не лежит одновременно и там, и там.
Если поставить
UNION, база будет тратить силы на проверку дублей, которые по бизнес-логике невозможны.Пример: отчёт по нескольким источникам
Допустим, у нас есть заказы из сайта и мобильного приложения.
SELECT user_id, amount, created_at, 'web' AS source FROM web_orders UNION ALL SELECT user_id, amount, created_at, 'mobile' AS source FROM mobile_orders;Так мы получаем общий поток заказов.
Потом его можно агрегировать:
WITH all_orders AS ( SELECT user_id, amount, created_at, 'web' AS source FROM web_orders UNION ALL SELECT user_id, amount, created_at, 'mobile' AS source FROM mobile_orders ) SELECT source, COUNT(*) AS orders_count, SUM(amount) AS revenue FROM all_orders GROUP BY source;Результат:
Здесь
UNION ALLособенно важен: мы считаем факты заказов, а не уникальные строки.Как проверить, нужны ли дубли
Если вы сомневаетесь, можно сначала посмотреть, есть ли пересечения.
Например, есть две таблицы email:
SELECT email FROM users UNION ALL SELECT email FROM leads;Чтобы найти повторяющиеся email после объединения:
WITH all_emails AS ( SELECT email FROM users UNION ALL SELECT email FROM leads ) SELECT email, COUNT(*) AS cnt FROM all_emails GROUP BY email HAVING COUNT(*) > 1;Так вы увидите, какие значения встречаются больше одного раза.
После этого уже можно осознанно решить:
source;UNION, UNION ALL и DISTINCT
По смыслу:
UNIONпохож на:
UNION ALL + DISTINCTТо есть такой запрос:
SELECT email FROM users UNION SELECT email FROM leads;примерно похож по идее на:
SELECT DISTINCT email FROM ( SELECT email FROM users UNION ALL SELECT email FROM leads ) AS all_emails;Но второй вариант иногда удобнее читать и расширять, особенно если вы хотите сначала собрать данные, а потом отдельно управлять дедупликацией.
Например, можно добавить
source, дату загрузки, приоритет и уже потом выбрать нужные строки.Отличия в PostgreSQL
В PostgreSQL
UNIONиUNION ALLработают строго и предсказуемо.PostgreSQL проверяет:
ORDER BY.При
UNIONPostgreSQL должен удалить дубликаты. В плане запроса это может выглядеть как сортировка или хеширование.Чтобы посмотреть, что реально делает база, можно использовать:
EXPLAIN SELECT country FROM users UNION SELECT country FROM employees;Или с фактическим выполнением:
EXPLAIN ANALYZE SELECT country FROM users UNION SELECT country FROM employees;Если таблицы большие, полезно сравнить план для
UNIONиUNION ALL.Часто вы увидите, что
UNION ALLпроще и дешевле.Отличия в MySQL
В MySQL идея такая же:
SELECT email FROM users UNION SELECT email FROM leads;удаляет дубликаты.
А:
SELECT email FROM users UNION ALL SELECT email FROM leads;оставляет все строки.
Но MySQL может быть мягче в неявных приведениях типов. Например, если в одной ветке число, а в другой текст, он может попытаться привести значения сам.
Иногда это удобно, но иногда приводит к неожиданным результатам. Поэтому для понятного запроса лучше явно выравнивать типы через
CAST.Ещё один практический момент: если в MySQL нужно применить
ORDER BYилиLIMITк отдельной ветке, используйте подзапрос.Например:
SELECT * FROM ( SELECT id, amount FROM orders WHERE status = 'paid' ORDER BY amount DESC LIMIT 5 ) AS top_paid UNION ALL SELECT id, amount FROM orders WHERE status = 'refunded';Так смысл будет явным.
Отличия в ClickHouse
В ClickHouse
UNION ALLвстречается особенно часто, потому что ClickHouse часто используют для аналитики и больших потоков данных.Пример:
SELECT user_id, event_name, created_at FROM web_events UNION ALL SELECT user_id, event_name, created_at FROM mobile_events;Для аналитических событий обычно важно не потерять строки, поэтому
UNION ALLчасто оказывается правильным выбором.Если нужно убрать дубликаты, в ClickHouse обычно явно используют
DISTINCTили агрегацию после объединения.Например:
SELECT DISTINCT email FROM ( SELECT email FROM users UNION ALL SELECT email FROM leads );Общее правило остаётся тем же:
Практические шаблоны
Объединить две таблицы без удаления дублей
SELECT email FROM users UNION ALL SELECT email FROM subscribers;Получить уникальный список email
SELECT email FROM users UNION SELECT email FROM subscribers;Добавить источник строки
SELECT id, email, 'users' AS source FROM users UNION ALL SELECT id, email, 'subscribers' AS source FROM subscribers;Объединить текущие и архивные заказы
SELECT id, user_id, amount, status, created_at FROM orders_current UNION ALL SELECT id, user_id, amount, status, created_at FROM orders_archive;Сделать общую ленту событий
SELECT created_at AS event_time, 'signup' AS event_type, email AS description FROM users UNION ALL SELECT created_at AS event_time, 'order' AS event_type, amount::text AS description FROM orders ORDER BY event_time;Отсортировать общий результат
SELECT id, name, salary FROM employees WHERE dept = 'eng' UNION ALL SELECT id, name, salary FROM employees WHERE dept = 'sales' ORDER BY salary DESC;Ограничить весь объединённый результат
SELECT id, name, salary FROM employees WHERE dept = 'eng' UNION ALL SELECT id, name, salary FROM employees WHERE dept = 'sales' ORDER BY salary DESC LIMIT 10;Ограничить только одну ветку
SELECT * FROM ( SELECT id, amount FROM orders WHERE status = 'paid' ORDER BY amount DESC LIMIT 5 ) AS top_paid UNION ALL SELECT id, amount FROM orders WHERE status = 'refunded';Что важно запомнить
UNIONиUNION ALLобъединяют результаты запросов вертикально.То есть строки второго
SELECTдобавляются под строки первогоSELECT.Главная разница:
UNION ALLобычно быстрее, потому что базе не нужно искать повторы.UNIONможет быть медленнее, потому что для удаления дублей базе нужно сравнить строки между собой.У всех
SELECTвнутри объединения должно быть:Имена итоговых колонок берутся из первого
SELECT.Если нужно понять, откуда пришла строка, добавляйте колонку-источник:
SELECT email, 'users' AS source FROM users UNION ALL SELECT email, 'leads' AS source FROM leads;Если нужен общий порядок,
ORDER BYпишется в самом конце.Короткий вывод
UNIONиUNION ALLнужны, когда вы хотите сложить результаты несколькихSELECTдруг под друга.В большинстве рабочих задач стоит начинать с
UNION ALL.Он проще, быстрее и не удаляет строки без вашего явного решения.
SELECT email FROM users UNION ALL SELECT email FROM leads;А
UNIONиспользуйте тогда, когда вам действительно нужен уникальный результат:SELECT email FROM users UNION SELECT email FROM leads;Главная мысль:
Если вы объединяете события, заказы, логи или архивы — чаще всего нужен
UNION ALL.Если вы собираете список уникальных email, стран, кодов или справочных значений — может подойти
UNION.Хороший SQL-разработчик выбирает между ними не наугад, а по смыслу данных: нужно ли сохранять все строки или действительно нужно убрать дубликаты.