Пошук уроків, статей та іншого контенту
Поєднуйте результати сумісних запитів за допомогою UNION та UNION ALL і керуйте дублюваннями.
UNIONUNION об’єднує результати двох або більше SELECT в один набір рядків.
Кожен запит має повертати:
однакову кількість стовпців;
сумісні типи даних у відповідних позиціях;
стовпці в однаковому логічному порядку.
Синтаксис:
SELECT column_1, column_2
FROM table_a
UNION
SELECT column_1, column_2
FROM table_b;За замовчуванням UNION видаляє дублікати з об’єднаного результату.
UNION і UNION ALLUNIONUNION повертає лише унікальні рядки:
SELECT 'email' AS channel
UNION
SELECT 'sms' AS channel
UNION
SELECT 'email' AS channel;Результат:
channel
-------
email
smsПовторне значення email було видалене.
UNION ALLUNION ALL зберігає всі рядки, включно з дублікати:
SELECT 'email' AS channel
UNION ALL
SELECT 'sms' AS channel
UNION ALL
SELECT 'email' AS channel;Результат:
channel
-------
email
sms
emailUNION ALL зазвичай працює швидше, оскільки PostgreSQL не витрачає ресурси на пошук і видалення дублікатів.
Використовуйте:
UNION, коли дублікати потрібно прибрати;
UNION ALL, коли кожен рядок важливий або дублікати мають залишитися.
Запити з UNION повинні повертати однакову кількість стовпців.
Цей запит некоректний:
SELECT id, name
FROM customers
UNION
SELECT id
FROM archived_customers;Перший SELECT повертає два стовпці, а другий — лише один.
Типи відповідних стовпців також мають бути сумісними:
SELECT id, name
FROM customers
UNION
SELECT archived_id, archived_name
FROM archived_customers;Тут id і archived_id повинні мати сумісні типи, як і name та archived_name.
За потреби тип можна привести явно:
SELECT id, name
FROM customers
UNION ALL
SELECT archived_id, archived_name::text
FROM archived_customers;Назви стовпців у фінальному результаті беруться з першого SELECT.
SELECT 1 AS user_id, 'Anna' AS user_name
UNION ALL
SELECT 2 AS id, 'Bohdan' AS name;Результат матиме назви:
user_id | user_name
--------+----------
1 | Anna
2 | BohdanАліаси з другого запиту не змінюють назви фінальних стовпців.
Тому зазвичай зрозумілі назви задають у першому запиті:
SELECT customer_id, customer_name
FROM customers
UNION ALL
SELECT archived_customer_id, archived_customer_name
FROM archived_customers;Припустімо, активні та архівні клієнти зберігаються окремо. Потрібно отримати єдиний список клієнтів.
WITH active_customers(customer_id, customer_name, email) AS (
VALUES
(1, 'Анна', 'anna@example.com'),
(2, 'Богдан', 'bohdan@example.com'),
(3, 'Олена', 'olena@example.com')
),
archived_customers(customer_id, customer_name, email) AS (
VALUES
(3, 'Олена', 'olena@example.com'),
(4, 'Марко', 'marko@example.com')
)
SELECT customer_id, customer_name, email
FROM active_customers
UNION
SELECT customer_id, customer_name, email
FROM archived_customers
ORDER BY customer_id;Результат:
customer_id | customer_name | email
------------+---------------+-------------------
1 | Анна | anna@example.com
2 | Богдан | bohdan@example.com
3 | Олена | olena@example.com
4 | Марко | marko@example.comКлієнт із customer_id = 3 присутній в обох джерелах, але UNION залишає один рядок, оскільки всі значення рядка однакові.
Якщо використати UNION ALL:
WITH active_customers(customer_id, customer_name, email) AS (
VALUES
(1, 'Анна', 'anna@example.com'),
(2, 'Богдан', 'bohdan@example.com'),
(3, 'Олена', 'olena@example.com')
),
archived_customers(customer_id, customer_name, email) AS (
VALUES
(3, 'Олена', 'olena@example.com'),
(4, 'Марко', 'marko@example.com')
)
SELECT customer_id, customer_name, email
FROM active_customers
UNION ALL
SELECT customer_id, customer_name, email
FROM archived_customers
ORDER BY customer_id;клієнт Олена з’явиться двічі.
UNION порівнює цілі рядки, а не лише один ідентифікатор.
SELECT 10 AS product_id, 'Basic' AS plan
UNION
SELECT 10 AS product_id, 'Premium' AS plan;Результат міститиме два рядки, тому що значення plan різні:
product_id | plan
------------+---------
10 | Basic
10 | PremiumЯкщо потрібно залишити лише один рядок для кожного product_id, одного UNION недостатньо. Він видаляє лише повністю однакові рядки.
До об’єднаного результату можна додати стовпець, який показує джерело рядка:
WITH active_customers(customer_id, customer_name) AS (
VALUES
(1, 'Анна'),
(2, 'Богдан')
),
archived_customers(customer_id, customer_name) AS (
VALUES
(3, 'Олена'),
(4, 'Марко')
)
SELECT customer_id, customer_name, 'active' AS source
FROM active_customers
UNION ALL
SELECT customer_id, customer_name, 'archived' AS source
FROM archived_customers
ORDER BY customer_id;Обидва запити повертають по три стовпці, тому вони сумісні.
Результат:
customer_id | customer_name | source
------------+---------------+---------
1 | Анна | active
2 | Богдан | active
3 | Олена | archived
4 | Марко | archivedЗверніть увагу: значення 'active' і 'archived' мають сумісний текстовий тип.
ORDER BY, який має сортувати весь результат, розміщують наприкінці:
SELECT 3 AS number
UNION ALL
SELECT 1 AS number
UNION ALL
SELECT 2 AS number
ORDER BY number;Результат:
number
------
1
2
3Без фінального ORDER BY порядок рядків не гарантований.
У загальному випадку ORDER BY застосовується до результату всього UNION, а не лише до останнього SELECT.
Якщо потрібно обмежити кількість рядків усього об’єднаного результату, LIMIT ставлять наприкінці:
SELECT 5 AS number
UNION ALL
SELECT 2 AS number
UNION ALL
SELECT 8 AS number
ORDER BY number
LIMIT 2;Результат:
number
------
2
5Якщо потрібно обмежити кожен окремий запит, використовуйте дужки:
(
SELECT 1 AS number
UNION ALL
SELECT 2 AS number
LIMIT 1
)
UNION ALL
(
SELECT 3 AS number
UNION ALL
SELECT 4 AS number
LIMIT 1
);У цьому випадку кожна частина спочатку обмежується одним рядком.
UNION з WHEREКожен SELECT може мати власні умови фільтрації:
SELECT id, name
FROM customers
WHERE country = 'Ukraine'
UNION ALL
SELECT id, name
FROM customers
WHERE country = 'Poland';Спочатку кожен запит вибирає рядки за власною умовою, потім PostgreSQL об’єднує результати.
Якщо джерело одне й умови мають однакову структуру, іноді простіше використати WHERE ... IN (...). Але UNION потрібен, коли об’єднуються різні запити або різні джерела даних.
UNION та UNION ALLРозгляньте такі запити:
SELECT customer_id
FROM current_orders
UNION
SELECT customer_id
FROM archived_orders;Такий варіант підходить, якщо потрібен список клієнтів, які коли-небудь робили замовлення. Один клієнт має з’явитися лише один раз.
SELECT order_id, customer_id, created_at
FROM current_orders
UNION ALL
SELECT order_id, customer_id, created_at
FROM archived_orders;Такий варіант підходить для отримання повного журналу замовлень, де кожен рядок є окремим замовленням.
SELECT id, name
FROM customers
UNION
SELECT id
FROM archived_customers;Виправте запити так, щоб вони повертали однакову кількість стовпців:
SELECT id, name
FROM customers
UNION
SELECT id, name
FROM archived_customers;SELECT id
FROM customers
UNION
SELECT created_at
FROM orders;id і created_at зазвичай мають несумісні типи. Потрібно вибрати стовпці з відповідними типами або виконати явне приведення, якщо це має зміст.
UNION видалить дублікати за ідентифікаторомSELECT customer_id, status
FROM current_customers
UNION
SELECT customer_id, status
FROM archived_customers;Якщо для одного customer_id відрізняється status, обидва рядки залишаться. UNION видаляє лише повністю однакові рядки.
UNION замість UNION ALLЯкщо дублікати не є проблемою, а важлива продуктивність або кількість рядків, використовуйте UNION ALL. Застосування UNION без потреби може вимагати додаткової обробки для видалення дублікатів.
SELECT id
FROM customers
ORDER BY id
UNION ALL
SELECT id
FROM archived_customers;Для сортування всього результату використовуйте один ORDER BY наприкінці:
SELECT id
FROM customers
UNION ALL
SELECT id
FROM archived_customers
ORDER BY id;UNION об’єднує результати кількох SELECT і видаляє повністю дублікати.
UNION ALL об’єднує результати без видалення дублікатів.
Усі частини операції повинні повертати однакову кількість сумісних стовпців.
Назви фінальних стовпців беруться з першого SELECT.
Дублікати порівнюються за всіма стовпцями рядка.
ORDER BY і LIMIT для всього результату розміщують після останнього SELECT.
Використовуйте UNION ALL, якщо видаляти дублікати не потрібно.