Комбинирование запросов (UNION, INTERSECT, EXCEPT)
Для объединения результатов двух и более запросов можно использовать операторы множеств UNION, INTERSECT и EXCEPT.
Чтобы воспроизвести приведенные в данной статье примеры и посмотреть, как в ClickHouse работают различные операторы объединения множеств, создайте предварительно таблицы:
-
products_2023
— каталог продуктов за 2023 год:CREATE TABLE products_2023 ( id UInt32, name String, category String, price Float32 ) ENGINE = MergeTree() ORDER BY id;
INSERT INTO products_2023 VALUES (1, 'Office Chair', 'Furniture', 170), (2, 'Backpack', 'Accessories', 60), (3, 'Monitor', 'Electronics', 200), (4, 'Tablet', 'Electronics', 750), (5, 'Water Bottle', 'Accessories', 10), (6, 'Office Desk', 'Furniture', 250);
┌─id─┬─name─────────┬─category────┬─price─┐ 1. │ 1 │ Office Chair │ Furniture │ 170 │ 2. │ 2 │ Backpack │ Accessories │ 60 │ 3. │ 3 │ Monitor │ Electronics │ 200 │ 4. │ 4 │ Tablet │ Electronics │ 750 │ 5. │ 5 │ Water Bottle │ Accessories │ 10 │ 6. │ 6 │ Office Desk │ Furniture │ 250 │ └────┴──────────────┴─────────────┴───────┘
-
products_2024
— каталог продуктов за 2024 год:CREATE TABLE products_2024 ( id UInt32, name String, category String, price Float32 ) ENGINE = MergeTree() ORDER BY id;
INSERT INTO products_2024 VALUES (1, 'Office Chair', 'Furniture', 175), (2, 'Backpack', 'Accessories', 60), (3, 'Monitor', 'Electronics', 220), (6, 'Office Desk', 'Furniture', 250), (7, 'Laptop', 'Electronics', 1000), (8, 'Smartphone', 'Electronics', 700), (9, 'Cap', 'Accessories', 6), (10, 'Coffee Table', 'Furniture', 180);
┌─id─┬─name─────────┬─category────┬─price─┐ 1. │ 1 │ Office Chair │ Furniture │ 175 │ 2. │ 2 │ Backpack │ Accessories │ 60 │ 3. │ 3 │ Monitor │ Electronics │ 220 │ 4. │ 6 │ Office Desk │ Furniture │ 250 │ 5. │ 7 │ Laptop │ Electronics │ 1000 │ 6. │ 8 │ Smartphone │ Electronics │ 700 │ 7. │ 9 │ Cap │ Accessories │ 6 │ 8. │ 10 │ Coffee Table │ Furniture │ 180 │ └────┴──────────────┴─────────────┴───────┘
-
sales_2024_may
— данные о продажах в мае 2024 года:CREATE TABLE sales_2024_may ( sale_id UInt32, product_name String, sale_date Date DEFAULT toDate(now()) ) ENGINE = MergeTree() ORDER BY sale_id;
INSERT INTO sales_2024_may VALUES (1, 'Smartphone', '2024-05-06'), (2, 'Smartphone', '2024-05-07'), (3, 'Monitor', '2024-05-07'), (4, 'Office Desk', '2024-05-08'), (5, 'Monitor', '2024-05-09'), (6, 'Office Chair', '2024-05-09'), (7, 'Backpack', '2024-05-10'), (8, 'Backpack', '2024-05-11'), (9, 'Cap', '2024-05-11'), (10, 'Cap', '2024-05-12');
┌─sale_id─┬─product_name─┬──sale_date─┐ 1. │ 1 │ Smartphone │ 2024-05-06 │ 2. │ 2 │ Smartphone │ 2024-05-07 │ 3. │ 3 │ Monitor │ 2024-05-07 │ 4. │ 4 │ Office Desk │ 2024-05-08 │ 5. │ 5 │ Monitor │ 2024-05-09 │ 6. │ 6 │ Office Chair │ 2024-05-09 │ 7. │ 7 │ Backpack │ 2024-05-10 │ 8. │ 8 │ Backpack │ 2024-05-11 │ 9. │ 9 │ Cap │ 2024-05-11 │ 10. │ 10 │ Cap │ 2024-05-12 │ └─────────┴──────────────┴────────────┘
UNION
Выражение UNION
добавляет к результату первого запроса результат второго запроса. Может использоваться в двух режимах:
-
UNION ALL
— в результат объединения включаются все строки из результатов обоих запросов, в том числе повторяющиеся; -
UNION DISTINCT
— из результата объединения исключаются повторяющиеся строки.
Если ключевое слово ALL
или DISTINCT
явно не указано, поведение UNION
определяется настройкой union_default_mode
, значением которой может быть ALL
, DISTINCT
или пустая строка (в этом случае при использовании UNION
генерируется исключение).
Синтаксис в общем виде:
SELECT <column1> [, <column2> ]
FROM <table1>
[WHERE <condition>]
UNION [ALL | DISTINCT]
SELECT <column1> [, <column2> ]
FROM <table2>
[WHERE <condition>]
Результирующие столбцы сопоставляются по их индексу (порядку внутри SELECT
). Если имена столбцов не совпадают, то имена для конечного результата берутся из первого запроса.
При объединении выполняется приведение типов. Например, если два запроса имеют одно и то же поле с совместимыми типами non-Nullable и Nullable, результат объединения будет иметь поле типа Nullable.
Запросы, которые являются частью UNION
, могут быть заключены в круглые скобки. ORDER BY
и LIMIT
применяются к отдельным запросам, а не к конечному результату. Если нужно применить преобразование к конечному результату, поместите все запросы с UNION
в подзапрос в выражении FROM
.
Примеры
UNION ALL
Следующий запрос объединяет каталоги товаров за 2023 и 2024 годы:
SELECT id, name FROM products_2023
UNION ALL
SELECT id, name FROM products_2024;
┌─id─┬─name─────────┐ 1. │ 1 │ Office Chair │ 2. │ 2 │ Backpack │ 3. │ 3 │ Monitor │ 4. │ 4 │ Tablet │ 5. │ 5 │ Water Bottle │ 6. │ 6 │ Office Desk │ └────┴──────────────┘ ┌─id─┬─name─────────┐ 7. │ 1 │ Office Chair │ 8. │ 2 │ Backpack │ 9. │ 3 │ Monitor │ 10. │ 6 │ Office Desk │ 11. │ 7 │ Laptop │ 12. │ 8 │ Smartphone │ 13. │ 9 │ Cap │ 14. │ 10 │ Coffee Table │ └────┴──────────────┘
UNION DISTINCT
Следующий запрос объединяет каталоги товаров за 2023 и 2024 годы, исключая дублирование:
SELECT id, name FROM products_2023
UNION DISTINCT
SELECT id, name FROM products_2024;
┌─id─┬─name─────────┐ 1. │ 1 │ Office Chair │ 2. │ 2 │ Backpack │ 3. │ 3 │ Monitor │ 4. │ 4 │ Tablet │ 5. │ 5 │ Water Bottle │ 6. │ 6 │ Office Desk │ └────┴──────────────┘ ┌─id─┬─name─────────┐ 7. │ 7 │ Laptop │ 8. │ 8 │ Smartphone │ 9. │ 9 │ Cap │ 10. │ 10 │ Coffee Table │ └────┴──────────────┘
INTERSECT
Выражение INTERSECT
возвращает все строки, которые есть как в результате первого запроса, так и в результате второго запроса. Запросы должны совпадать по количеству столбцов, их порядку и типам данных. Результат INTERSECT
может содержать дубликаты строк. Чтобы исключить дубликаты, используйте INTERSECT DISTINCT
.
Если используется несколько выражений INTERSECT
и скобки не указаны, вычисление пересечений выполняется слева направо. INTERSECT
имеет более высокий приоритет выполнения, чем UNION
и EXCEPT
.
Синтаксис в общем виде:
SELECT <column1> [, <column2> ]
FROM <table1>
[WHERE <condition>]
INTERSECT [DISTINCT]
SELECT <column1> [, <column2> ]
FROM <table2>
[WHERE <condition>]
Примеры
Следующий запрос возвращает названия продуктов, проданных в мае 2024 года, которые входят в категорию Electronics
по данным таблицы products_2024
:
SELECT product_name FROM sales_2024_may
INTERSECT
SELECT name FROM products_2024 WHERE category='Electronics';
В результате видно, что было по две продажи продуктов Smartphone
и Monitor
:
┌─product_name─┐ 1. │ Smartphone │ 2. │ Smartphone │ 3. │ Monitor │ 4. │ Monitor │ └──────────────┘
Ключевое слово DISTINCT
исключает дубликаты строк из результата предыдущего запроса:
SELECT product_name FROM sales_2024_may
INTERSECT DISTINCT
SELECT name FROM products_2024 WHERE category='Electronics';
┌─product_name─┐ 1. │ Smartphone │ 2. │ Monitor │ └──────────────┘
EXCEPT
Выражение EXCEPT
возвращает результат первого запроса, исключая строки, которые также входят в результат второго запроса. Запросы должны совпадать по количеству столбцов, их порядку и типам данных. Результат EXCEPT
может содержать дубликаты строк. Чтобы исключить дубликаты, используйте EXCEPT DISTINCT
.
Если используется несколько выражений EXCEPT
и скобки не указаны, EXCEPT
выполняется по порядку слева направо. У EXCEPT
такой же приоритет выполнения, как у UNION
— более низкий, чем у INTERSECT
.
Синтаксис в общем виде:
SELECT <column1> [, <column2> ]
FROM <table1>
[WHERE <condition>]
EXCEPT [DISTINCT]
SELECT <column1> [, <column2> ]
FROM <table2>
[WHERE <condition>]
Примеры
EXCEPT
Следующий запрос возвращает названия проданных в мае 2024 года продуктов, которые не входят в категорию Electronics
:
SELECT product_name FROM sales_2024_may
EXCEPT
SELECT name FROM products_2024 WHERE category='Electronics';
┌─product_name─┐ 1. │ Office Desk │ 2. │ Office Chair │ 3. │ Backpack │ 4. │ Backpack │ 5. │ Cap │ 6. │ Cap │ └──────────────┘
Используйте ключевое слово DISTINCT
, чтобы убрать дублирование из результата предыдущего запроса:
SELECT product_name FROM sales_2024_may
EXCEPT DISTINCT
SELECT name FROM products_2024 WHERE category='Electronics';
┌─product_name─┐ 1. │ Office Desk │ 2. │ Office Chair │ 3. │ Backpack │ 4. │ Cap │ └──────────────┘