16 KiB
Секция JOIN
Join создаёт новую таблицу путем объединения столбцов из одной или нескольких таблиц с использованием общих для каждой из них значений. Это обычная операция в базах данных с поддержкой SQL, которая соответствует join из реляционной алгебры. Частный случай соединения одной таблицы часто называют «self-join».
Синтаксис:
SELECT <expr_list>
FROM <left_table>
[GLOBAL] [INNER|LEFT|RIGHT|FULL|CROSS] [OUTER|SEMI|ANTI|ANY|ASOF] JOIN <right_table>
(ON <expr_list>)|(USING <column_list>) ...
Выражения из секции ON
и столбцы из секции USING
называется «ключами соединения». Если не указано иное, при присоединение создаётся Декартово произведение из строк с совпадающими значениями ключей соединения, что может привести к получению результатов с гораздо большим количеством строк, чем исходные таблицы.
Поддерживаемые типы соединения
Все типы из стандартого SQL JOIN поддерживаются:
INNER JOIN
, возвращаются только совпадающие строки.LEFT OUTER JOIN
, не совпадающие строки из левой таблицы возвращаются в дополнение к совпадающим строкам.RIGHT OUTER JOIN
, не совпадающие строки из правой таблицы возвращаются в дополнение к совпадающим строкам.FULL OUTER JOIN
, не совпадающие строки из обеих таблиц возвращаются в дополнение к совпадающим строкам.CROSS JOIN
, производит декартово произведение таблиц целиком, ключи соединения не указываются.
Без указания типа JOIN
подразумевается INNER
. Ключевое слово OUTER
можно опускать. Альтернативным синтаксисом для CROSS JOIN
является ли указание нескольких таблиц, разделённых запятыми, в секции FROM.
Дополнительные типы соединений, доступные в ClickHouse:
LEFT SEMI JOIN
иRIGHT SEMI JOIN
, белый список по ключам соединения, не производит декартово произведение.LEFT ANTI JOIN
иRIGHT ANTI JOIN
, черный список по ключам соединения, не производит декартово произведение.LEFT ANY JOIN
,RIGHT ANY JOIN
иINNER ANY JOIN
, Частично (для противоположных сторонLEFT
иRIGHT
) или полностью (дляINNER
иFULL
) отключает декартово произведение для стандартых видовJOIN
.ASOF JOIN
иLEFT ASOF JOIN
, Для соединения последовательностей по нечеткому совпадению. ИспользованиеASOF JOIN
описано ниже.
Настройки
!!! note "Примечание" Значение строгости по умолчанию может быть переопределено с помощью настройки join_default_strictness.
Поведение сервера ClickHouse для операций ANY JOIN
зависит от параметра any_join_distinct_right_table_keys.
Использование ASOF JOIN
ASOF JOIN
применим в том случае, когда необходимо объединять записи, которые не имеют точного совпадения.
Для работы алгоритма необходим специальный столбец в таблицах. Этот столбец:
- Должен содержать упорядоченную последовательность.
- Может быть одного из следующих типов: Int*, UInt*, Float*, Date, DateTime, Decimal*.
- Не может быть единственным столбцом в секции
JOIN
.
Синтаксис ASOF JOIN ... ON
:
SELECT expressions_list
FROM table_1
ASOF LEFT JOIN table_2
ON equi_cond AND closest_match_cond
Можно использовать произвольное количество условий равенства и одно условие на ближайшее совпадение. Например, SELECT count() FROM table_1 ASOF LEFT JOIN table_2 ON table_1.a == table_2.b AND table_2.t <= table_1.t
.
Условия, поддержанные для проверки на ближайшее совпадение: >
, >=
, <
, <=
.
Синтаксис ASOF JOIN ... USING
:
SELECT expressions_list
FROM table_1
ASOF JOIN table_2
USING (equi_column1, ... equi_columnN, asof_column)
Для слияния по равенству ASOF JOIN
использует equi_columnX
, а для слияния по ближайшему совпадению использует asof_column
с условием table_1.asof_column >= table_2.asof_column
. Столбец asof_column
должен быть последним в секции USING
.
Например, рассмотрим следующие таблицы:
table_1 table_2
event | ev_time | user_id event | ev_time | user_id
----------|---------|---------- ----------|---------|----------
... ...
event_1_1 | 12:00 | 42 event_2_1 | 11:59 | 42
... event_2_2 | 12:30 | 42
event_1_2 | 13:00 | 42 event_2_3 | 13:00 | 42
... ...
ASOF JOIN
принимает метку времени пользовательского события из table_1
и находит такое событие в table_2
метка времени которого наиболее близка к метке времени события из table_1
в соответствии с условием на ближайшее совпадение. При этом столбец user_id
используется для объединения по равенству, а столбец ev_time
для объединения по ближайшему совпадению. В нашем примере event_1_1
может быть объединено с event_2_1
, event_1_2
может быть объединено с event_2_3
, а event_2_2
не объединяется.
!!! note "Примечание"
ASOF JOIN
не поддержан для движка таблиц Join.
Чтобы задать значение строгости по умолчанию, используйте сессионный параметр join_default_strictness.
Распределённый join
Есть два пути для выполнения соединения с участием распределённых таблиц:
- При использовании обычного
JOIN
, запрос отправляется на удалённые серверы. На каждом из них выполняются подзапросы для формирования «правой» таблицы, и с этой таблицей выполняется соединение. То есть, «правая» таблица формируется на каждом сервере отдельно. - При использовании
GLOBAL ... JOIN
, сначала сервер-инициатор запроса запускает подзапрос для вычисления правой таблицы. Эта временная таблица передаётся на каждый удалённый сервер, и на них выполняются запросы с использованием переданных временных данных.
Будьте аккуратны при использовании GLOBAL
. За дополнительной информацией обращайтесь в раздел Распределенные подзапросы.
Рекомендации по использованию
Обработка пустых ячеек и NULL
При соединении таблиц могут появляться пустые ячейки. Настройка join_use_nulls определяет, как ClickHouse заполняет эти ячейки.
Если ключами JOIN
выступают поля типа Nullable, то строки, где хотя бы один из ключей имеет значение NULL, не соединяются.
Синтаксис
Требуется, чтобы столбцы, указанные в USING
, назывались одинаково в обоих подзапросах, а остальные столбцы - по-разному. Изменить имена столбцов в подзапросах можно с помощью синонимов.
В секции USING
указывается один или несколько столбцов для соединения, что обозначает условие на равенство этих столбцов. Список столбцов задаётся без скобок. Более сложные условия соединения не поддерживаются.
Ограничения cинтаксиса
Для множественных секций JOIN
в одном запросе SELECT
:
- Получение всех столбцов через
*
возможно только при объединении таблиц, но не подзапросов. - Секция
PREWHERE
недоступна.
Для секций ON
, WHERE
и GROUP BY
:
- Нельзя использовать произвольные выражения в секциях
ON
,WHERE
, иGROUP BY
, однако можно определить выражение в секцииSELECT
и затем использовать его через алиас в других секциях.
Производительность
При запуске JOIN
, отсутствует оптимизация порядка выполнения по отношению к другим стадиям запроса. Соединение (поиск в «правой» таблице) выполняется до фильтрации в WHERE
и до агрегации. Чтобы явно задать порядок вычислений, рекомендуется выполнять JOIN
подзапроса с подзапросом.
Каждый раз для выполнения запроса с одинаковым JOIN
, подзапрос выполняется заново — результат не кэшируется. Это можно избежать, используя специальный движок таблиц Join, представляющий собой подготовленное множество для соединения, которое всегда находится в оперативке.
В некоторых случаях это более эффективно использовать IN вместо JOIN
.
Если JOIN
необходим для соединения с таблицами измерений (dimension tables - сравнительно небольшие таблицы, которые содержат свойства измерений - например, имена для рекламных кампаний), то использование JOIN
может быть не очень удобным из-за громоздкости синтаксиса, а также из-за того, что правая таблица читается заново при каждом запросе. Специально для таких случаев существует функциональность «Внешние словари», которую следует использовать вместо JOIN
. Дополнительные сведения смотрите в разделе «Внешние словари».
Ограничения по памяти
По умолчанию ClickHouse использует алгоритм hash join. ClickHouse берет <right_table>
и создает для него хэш-таблицу в оперативной памяти. После некоторого порога потребления памяти ClickHouse переходит к алгоритму merge join.
- max_rows_in_join — ограничивает количество строк в хэш-таблице.
- max_bytes_in_join — ограничивает размер хэш-таблицы.
По достижении любого из этих ограничений, ClickHouse действует в соответствии с настройкой join_overflow_mode.
Примеры
Пример:
SELECT
CounterID,
hits,
visits
FROM
(
SELECT
CounterID,
count() AS hits
FROM test.hits
GROUP BY CounterID
) ANY LEFT JOIN
(
SELECT
CounterID,
sum(Sign) AS visits
FROM test.visits
GROUP BY CounterID
) USING CounterID
ORDER BY hits DESC
LIMIT 10
┌─CounterID─┬───hits─┬─visits─┐
│ 1143050 │ 523264 │ 13665 │
│ 731962 │ 475698 │ 102716 │
│ 722545 │ 337212 │ 108187 │
│ 722889 │ 252197 │ 10547 │
│ 2237260 │ 196036 │ 9522 │
│ 23057320 │ 147211 │ 7689 │
│ 722818 │ 90109 │ 17847 │
│ 48221 │ 85379 │ 4652 │
│ 19762435 │ 77807 │ 7026 │
│ 722884 │ 77492 │ 11056 │
└───────────┴────────┴────────┘