UNION и JOIN в PostgreSQL: разница на живом примере
В PostgreSQL UNION складывает строки из совместимых запросов, а JOIN соединяет столбцы связанных таблиц. На примере заказов разберём, когда нужны оба подхода, чем отличаются UNION и UNION ALL и какие ошибки проверить.

Одна задача — два разных результата
Представьте магазин, который хранит активные и архивные заказы в отдельных таблицах. Если нужно получить общий список заказов, пригодится UNION. Если к каждому заказу нужно добавить имя покупателя из таблицы клиентов — нужен JOIN. Подмена одного другим обычно не даёт ошибку: она даёт результат не той формы, который легко принять за правильный.
Примеры ниже рассчитаны на PostgreSQL 16. Создадим таблицы и сравним запросы на одних и тех же данных.
Исходные данные: активные и архивные заказы
У двух таблиц заказов одинаковая структура. Это важное условие для объединения: запросы должны возвращать одинаковое число столбцов, а типы соответствующих столбцов должны быть совместимы.
CREATE TABLE customers (
id integer PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE active_orders (
id integer PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
total numeric(10, 2) NOT NULL
);
CREATE TABLE archived_orders (
id integer PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
total numeric(10, 2) NOT NULL
);
INSERT INTO customers (id, name) VALUES
(1, 'Анна'),
(2, 'Илья');
INSERT INTO active_orders (id, customer_id, total) VALUES
(101, 1, 2500.00),
(102, 2, 1800.00);
INSERT INTO archived_orders (id, customer_id, total) VALUES
(201, 1, 900.00);
Теперь нужно показать все заказы — независимо от того, в какой таблице они лежат. Это объединение наборов строк, а не связывание заказа с клиентом.
UNION: собрать строки из нескольких запросов
Каждая часть запроса возвращает три столбца в одинаковом порядке: идентификатор заказа, идентификатор клиента и сумма. UNION ALL добавляет строки друг к другу и сохраняет повторы.
SELECT id, customer_id, total
FROM active_orders
UNION ALL
SELECT id, customer_id, total
FROM archived_orders
ORDER BY id;
Результат содержит три заказа. Чтобы получить обычный UNION, достаточно заменить UNION ALL на UNION. Тогда PostgreSQL удалит одинаковые строки результата. Это не проверка того, что совпадает только id: сравнивается вся строка из выбранных столбцов.
В нашем примере строки разные, поэтому оба варианта вернут три записи. Если повторов нет по смыслу данных, UNION ALL обычно точнее выражает задачу и не просит базу удалять дубликаты.
JOIN: добавить связанные столбцы
Чтобы вывести имя покупателя рядом с каждым заказом, запросу нужно сопоставить строки по ключу customer_id. Вот здесь работает JOIN:
SELECT o.id AS order_id, c.name, o.total
FROM active_orders AS o
JOIN customers AS c ON c.id = o.customer_id
ORDER BY o.id;
Этот запрос вернёт два активных заказа с именами покупателей. Архивные заказы в результат не попадут, потому что источник — только active_orders. Для всех заказов сначала соберите общий набор, а затем соедините его с клиентами:
SELECT o.id AS order_id, c.name, o.total
FROM (
SELECT id, customer_id, total
FROM active_orders
UNION ALL
SELECT id, customer_id, total
FROM archived_orders
) AS o
JOIN customers AS c ON c.id = o.customer_id
ORDER BY o.id;
Здесь операции решают разные задачи: внутренний запрос складывает заказы из двух источников, внешний присоединяет к каждой строке сведения о покупателе.
Как выбрать между UNION и JOIN
| Вопрос | UNION / UNION ALL | JOIN |
|---|---|---|
| Что объединяется | Строки из результатов запросов | Столбцы связанных строк |
| Что должно совпадать | Число столбцов и совместимость типов по позициям | Условие связи, например равенство ключей |
| Типичный пример | Активные и архивные заказы | Заказ и данные его покупателя |
| Что происходит с повторами | UNION удаляет одинаковые строки; UNION ALL сохраняет | Строки могут размножиться, если условию соответствуют несколько строк справа |
Перед написанием запроса задайте себе простой вопрос: нужны ещё строки из другого источника или дополнительные поля к уже выбранным строкам? В первом случае проверяйте UNION, во втором — JOIN.
Если вы заменяете UNION на JOIN или наоборот, сначала проверьте форму результата: набор строк и набор столбцов — не одно и то же.
Что ломается в практике
Столбцы в UNION стоят в разном порядке
PostgreSQL сопоставляет столбцы по позиции, а не по имени. Если в одном запросе первым идёт customer_id, а в другом — id, результат может пройти проверку типов, но получить неверный смысл. Явно перечисляйте столбцы в одинаковом порядке в обеих частях.
Несовместимые типы
Например, идентификатор в одной таблице хранится как число, а в другой — как текст. PostgreSQL может сообщить, что типы не совпадают. Приводите тип явно только после проверки данных: текстовый идентификатор не всегда безопасно преобразуется в число.
Ожидали один заказ, получили несколько строк
В JOIN одна строка слева повторяется для каждой подходящей строки справа. Если соединить заказ с таблицей позиций, заказ с тремя позициями даст три строки. Это не обязательно ошибка — возможно, так и задумано. Если нужен один итог на заказ, агрегируйте позиции отдельно или продумайте группировку.
UNION неожиданно убрал дубликаты
Проверьте, не использован ли UNION там, где нужно сохранить все записи. И наоборот: переход на UNION ALL может вернуть повторы, если один и тот же заказ оказался в обоих источниках. Удаление повторов не заменяет проверку качества данных.
Порядок строк кажется случайным
Без ORDER BY не полагайтесь на порядок результата. Для объединённого запроса задайте сортировку в конце всего выражения. Если сортируете по вычисляемому или переименованному столбцу, явно проверьте, что имя доступно во внешнем результате.
Когда что выбирать
- UNION ALL — складываете записи из таблиц одинакового назначения и хотите сохранить все строки.
- UNION — хотите убрать полностью одинаковые строки результата и понимаете, что сравниваются все выбранные столбцы.
- JOIN — к строкам одной таблицы нужны связанные данные из другой: имя клиента, адрес или сведения о товаре.
- UNION ALL, затем JOIN — сначала собираете однородные записи из нескольких источников, потом добавляете к ним общие справочные поля.
Термин: кардинальность результата — число строк, которое возвращает запрос. Для JOIN оно зависит от количества совпадений по условию связи; поэтому неожиданное увеличение кардинальности часто указывает на связь «один ко многим» или на неполное условие.
Что проверить дальше
Возьмите один запрос из проекта и запишите ожидаемые столбцы и число строк до его запуска. Для JOIN проверьте уникальность ключа в присоединяемой таблице; для UNION сравните порядок и типы столбцов обеих частей. Затем посмотрите план через EXPLAIN, если запрос работает медленно: сравнение строк, соединение и сортировка могут иметь разную стоимость на реальном объёме данных. Инвалидация или объединение таблиц на уровне схемы — отдельная тема; здесь речь только о выборе между операторами запроса.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: PostgreSQL 16: Combining Queries, PostgreSQL 16: Table Expressions
Частые вопросы
Можно ли использовать UNION, если столбцы называются по-разному?
Чем UNION ALL быстрее UNION?
Можно ли объединить UNION и JOIN в одном запросе?
Почему JOIN вернул больше строк, чем было в исходной таблице?
Можно ли применять ORDER BY внутри каждой части UNION?
Работают ли эти примеры в других версиях PostgreSQL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.