UNION объединяет результаты двух и более SELECT-запросов в один набор строк. Это как сложить два списка — но с условиями совместимости типов данных.
Синтаксис
SELECT column1, column2 FROM table1
UNION
SELECT column1, column2 FROM table2;
Правила:
- Количество столбцов в обоих запросах должно совпадать
- Типы данных должны быть совместимы (или автоматически приводимы)
- Имена столбцов в результате берутся из первого запроса
UNION vs UNION ALL: главное отличие
-- UNION: убирает дубликаты (дороже)
SELECT city FROM customers
UNION
SELECT city FROM suppliers;
-- UNION ALL: оставляет все строки (быстрее)
SELECT city FROM customers
UNION ALL
SELECT city FROM suppliers;
Пример:
customers: Москва, Питер, Москва suppliers: Москва, Екатеринбург
| UNION (без дублей) | UNION ALL (с дублями) |
|---|---|
| Москва | Москва |
| Питер | Питер |
| Екатеринбург | Москва |
| Москва | |
| Екатеринбург |
UNION ALL всегда быстрее — он не делает сортировку/хеширование для поиска дублей. Если дубликаты не важны или их не будет — всегда используйте UNION ALL.
Практические примеры
Объединение двух таблиц одной структуры
-- Все транзакции: пополнения и списания в одном списке
SELECT
created_at,
amount,
'deposit' AS type
FROM deposits
WHERE user_id = 42
UNION ALL
SELECT
created_at,
amount,
'withdrawal' AS type
FROM withdrawals
WHERE user_id = 42
ORDER BY created_at DESC;
«Стек» запросов для разных условий
-- VIP-клиенты: либо потратили > 100к, либо зарегистрированы > 2 лет назад
SELECT user_id, 'big_spender' AS reason
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 100000
UNION
SELECT user_id, 'loyal_customer' AS reason
FROM users
WHERE created_at < NOW() - INTERVAL '2 years';
Добавить «синтетическую» строку
-- Список с итоговой строкой
SELECT department, SUM(salary) AS total
FROM employees
GROUP BY department
UNION ALL
SELECT 'ИТОГО', SUM(salary)
FROM employees
ORDER BY department;
ORDER BY с UNION
ORDER BY применяется ко всему результату и пишется в конце:
SELECT name, 'employee' AS source FROM employees
UNION ALL
SELECT name, 'contractor' AS source FROM contractors
ORDER BY name ASC; -- ← сортирует весь объединённый результат
Нельзя сортировать каждую часть отдельно (только через подзапрос):
-- Ошибка:
SELECT name FROM employees ORDER BY name
UNION ALL
SELECT name FROM contractors;
-- Правильно (если нужен разный порядок):
SELECT * FROM (SELECT name, 1 AS priority FROM employees ORDER BY name LIMIT 10) e
UNION ALL
SELECT * FROM (SELECT name, 2 AS priority FROM contractors ORDER BY name LIMIT 10) c;
INTERSECT и EXCEPT — родственники UNION
В стандарте SQL есть ещё два оператора работы с множествами:
-- INTERSECT: только строки, которые есть в ОБОИХ результатах
SELECT user_id FROM buyers
INTERSECT
SELECT user_id FROM newsletter_subscribers;
-- EXCEPT: строки из первого, которых нет во втором
SELECT user_id FROM all_users
EXCEPT
SELECT user_id FROM active_users;
-- = пользователи, которые никогда не были активны
PostgreSQL поддерживает все три оператора. Аналоги INTERSECT ALL и EXCEPT ALL (с дублями) тоже доступны.
Производительность
-- Медленно: UNION делает дедупликацию
SELECT email FROM customers UNION SELECT email FROM leads;
-- Быстрее: UNION ALL если дублей нет или они не важны
SELECT email FROM customers UNION ALL SELECT email FROM leads;
Для больших таблиц разница может быть кратной. Проверяйте план через EXPLAIN ANALYZE:
EXPLAIN ANALYZE
SELECT email FROM customers UNION SELECT email FROM leads;
В плане UNION выглядит как HashAggregate или Sort + Unique, UNION ALL — просто Append.
Частые ошибки
Разное число столбцов
-- Ошибка: в первом 3 столбца, во втором 2
SELECT id, name, email FROM users
UNION
SELECT id, email FROM admins;
Несовместимые типы
-- Ошибка: нельзя объединить int и varchar без явного приведения
SELECT id FROM users
UNION
SELECT name FROM users;
-- Правильно:
SELECT id::text FROM users
UNION
SELECT name FROM users;
Потеря имён столбцов
Имена берутся из первого запроса — это важно при ORDER BY:
SELECT order_id AS id, total FROM orders
UNION ALL
SELECT transaction_id, amount FROM refunds
ORDER BY id; -- "id" — это имя из первого запроса (order_id)
Итог
| UNION | UNION ALL | |
|---|---|---|
| Убирает дубликаты | Да | Нет |
| Скорость | Медленнее | Быстрее |
| Когда использовать | Когда дубликаты недопустимы | Всегда когда возможно |
UNION ALL — правило по умолчанию. UNION — только когда дубликаты реально нужно убрать и вы готовы заплатить за сортировку.