Работа с операциями JOIN в ClickHouse
ClickHouse полностью поддерживает стандартные операции JOIN в SQL, что позволяет эффективно анализировать данные. В этом руководстве вы ознакомитесь с некоторыми из наиболее распространённых типов JOIN и узнаете, как использовать их с помощью диаграмм Венна и примеров запросов к нормализованному набору данных IMDb из репозитория реляционных наборов данных.
Тестовые данные и ресурсы
Инструкции по созданию и загрузке таблиц можно найти здесь. Набор данных также доступен в playground для пользователей, которые не хотят создавать и загружать таблицы локально.
Вы будете использовать следующие четыре таблицы из примерного набора данных:

Данные в этих четырёх таблицах описывают фильмы, которые могут относиться к одному или нескольким жанрам. Роли в фильме исполняются актёрами.
Стрелки на диаграмме выше обозначают связи внешнего ключа с первичным ключом, например, столбец movie_id строки в таблице genres содержит значение id из строки таблицы movies.
Между фильмами и актёрами существует отношение многие-ко-многим.
Это отношение многие-ко-многим нормализовано в два отношения один-ко-многим с помощью таблицы roles.
Каждая строка в таблице roles содержит значения столбцов id из таблиц movies и actors.
Типы соединений, поддерживаемые в ClickHouse
ClickHouse поддерживает следующие типы соединений (JOIN):
В следующих разделах будут приведены примерные запросы для каждого из перечисленных выше типов JOIN.
INNER JOIN
Оператор INNER JOIN для каждой пары строк, совпадающих по ключам соединения, возвращает значения столбцов строки из левой таблицы, объединённые со значениями столбцов строки из правой таблицы.
Если у строки более одного совпадения, то возвращаются все совпадения (то есть для строк с совпадающими ключами соединения получается декартово произведение).

Этот запрос находит жанр или жанры для каждого фильма, выполняя соединение таблицы movies с таблицей genres:
Ключевое слово INNER можно опустить.
Поведение INNER JOIN может быть расширено или изменено с помощью одного из следующих типов соединения.
(LEFT / RIGHT / FULL) OUTER JOIN
LEFT OUTER JOIN ведёт себя как INNER JOIN, а для несовпадающих строк левой таблицы ClickHouse возвращает значения по умолчанию для столбцов правой таблицы.
Запрос RIGHT OUTER JOIN аналогичен и также возвращает значения из несовпадающих строк правой таблицы вместе со значениями по умолчанию для столбцов левой таблицы.
Запрос FULL OUTER JOIN сочетает в себе LEFT и RIGHT OUTER JOIN и возвращает значения из несовпадающих строк как левой, так и правой таблиц вместе со значениями по умолчанию для столбцов правой и левой таблиц соответственно.

ClickHouse можно настроить так, чтобы он возвращал NULL вместо значений по умолчанию (однако по соображениям производительности это менее предпочтительно).
Этот запрос находит все фильмы без жанра, выбирая все строки из таблицы movies, для которых нет соответствующих строк в таблице genres, и которые, следовательно, при выполнении запроса получают значение по умолчанию 0 для столбца movie_id:
Ключевое слово OUTER можно не указывать.
CROSS JOIN
CROSS JOIN создаёт полное декартово произведение двух таблиц, не учитывая ключи соединения.
Каждая строка из левой таблицы объединяется с каждой строкой из правой таблицы.

Следующий запрос, таким образом, объединяет каждую строку из таблицы movies с каждой строкой из таблицы genres:
Хотя предыдущий пример запроса сам по себе был не особо полезен, его можно дополнить оператором WHERE, чтобы сопоставить соответствующие строки и воспроизвести поведение INNER JOIN для поиска жанров для каждого фильма:
Альтернативный синтаксис для CROSS JOIN задаёт несколько таблиц в предложении FROM, перечисляя их через запятую.
ClickHouse переписывает CROSS JOIN в INNER JOIN, если в предложении WHERE запроса есть выражения соединения.
Вы можете проверить это для примерного запроса с помощью EXPLAIN SYNTAX (он возвращает синтаксически оптимизированную версию, в которую запрос переписывается перед выполнением):
Оператор INNER JOIN в синтаксически оптимизированной версии запроса с CROSS JOIN содержит ключевое слово ALL, которое было явно добавлено, чтобы сохранить семантику декартова произведения для CROSS JOIN даже при его преобразовании в INNER JOIN, для которого декартово произведение может быть отключено.
И поскольку, как упоминалось выше, ключевое слово OUTER можно опустить для RIGHT OUTER JOIN, а ключевое слово ALL при необходимости добавить, вы можете написать ALL RIGHT JOIN, и это будет работать корректно.
(LEFT / RIGHT) SEMI JOIN
Запрос LEFT SEMI JOIN возвращает значения столбцов для каждой строки из левой таблицы, у которой есть хотя бы одно совпадение по ключу соединения в правой таблице.
Возвращается только первое найденное совпадение (декартово произведение не формируется).
Запрос RIGHT SEMI JOIN аналогичен и возвращает значения столбцов для всех строк из правой таблицы с хотя бы одним совпадением в левой таблице, при этом также возвращается только первое найденное совпадение.

Этот запрос находит всех актёров и актрис, которые снимались в фильме в 2023 году.
Обратите внимание, что при обычном соединении (INNER) один и тот же актёр/актриса появлялся бы несколько раз, если у него/неё было более одной роли в 2023 году:
(LEFT / RIGHT) ANTI JOIN
LEFT ANTI JOIN возвращает значения столбцов для всех несовпадающих строк из левой таблицы.
Аналогично, RIGHT ANTI JOIN возвращает значения столбцов для всех несовпадающих строк из правой таблицы.

Альтернативная формулировка предыдущего примера запроса с внешним соединением — использование ANTI JOIN для поиска фильмов, у которых нет жанра в наборе данных:
(LEFT / RIGHT / INNER) ANY JOIN
LEFT ANY JOIN — это комбинация LEFT OUTER JOIN и LEFT SEMI JOIN. Это означает, что ClickHouse возвращает значения столбцов для каждой строки из левой таблицы, либо объединяя их со значениями столбцов совпадающей строки из правой таблицы, либо объединяя их со значениями столбцов по умолчанию для правой таблицы, если совпадающей строки не существует.
Если строка из левой таблицы имеет более одного совпадения в правой таблице, ClickHouse возвращает только объединённые значения столбцов из первого найденного совпадения (декартово произведение отключено).
Аналогично, RIGHT ANY JOIN — это комбинация RIGHT OUTER JOIN и RIGHT SEMI JOIN.
А INNER ANY JOIN — это INNER JOIN с отключённым декартовым произведением.

Следующий пример демонстрирует LEFT ANY JOIN на абстрактном примере с использованием двух временных таблиц (left_table и right_table), созданных с помощью values — табличной функции:
Это тот же запрос с использованием RIGHT ANY JOIN:
Вот запрос с INNER ANY JOIN:
ASOF JOIN
ASOF JOIN предоставляет возможности неточного сопоставления.
Если строка из левой таблицы не имеет точного совпадения в правой таблице, то вместо этого в качестве совпадения используется ближайшая по значению строка из правой таблицы.
Это особенно полезно для аналитики временных рядов и может значительно снизить сложность запросов.

Следующий пример показывает аналитику временных рядов биржевых данных по акциям.
Таблица quotes содержит котировки биржевых тикеров в определённые моменты времени в течение дня.
В примере данные о цене обновляются каждые 10 секунд.
Таблица trades перечисляет сделки с тикерами — указаны сделки по определённому объёму тикера, купленному в конкретный момент времени:

Чтобы вычислить точную стоимость каждой сделки, нужно сопоставить сделки с ближайшим по времени значением котировки.
Это просто и компактно делается с помощью ASOF JOIN, где вы используете предложение ON для указания условия точного совпадения и предложение AND для указания условия ближайшего совпадения: для конкретного тикера (точное совпадение) вы ищете строку с «ближайшим» временем из таблицы quotes ровно в момент сделки или до него (неточное совпадение) для сделки с этим тикером:
Клауза ON в ASOF JOIN является обязательной и задаёт условие точного соответствия в дополнение к неточному условию соответствия в клаузе AND.
Краткое описание
В этом руководстве описано, как ClickHouse поддерживает все стандартные типы операций SQL JOIN, а также специализированные варианты для выполнения аналитических запросов. Подробнее об операциях JOIN см. в документации по команде JOIN.