ARRAY JOIN и arrayJoin()
Массивы — базовый тип данных в ClickHouse. Таблицы часто содержат столбцы типа Array(String), Array(UInt32), Array(Tuple(String, UInt32)). Но анализировать данные удобнее в строковом формате: одна строка — один элемент. Для этого существует ARRAY JOIN.
ARRAY JOIN разворачивает массив в отдельные строки: одна строка с массивом из 3 элементов превращается в 3 строки. Это аналог UNNEST в PostgreSQL и LATERAL VIEW explode() в Spark.
Как работает ARRAY JOIN
SELECT user_id, tag
FROM events
ARRAY JOIN tags AS tag;
| user_id | tag |
|---|---|
| 1 | news |
| 1 | sport |
| 1 | tech |
Скалярные столбцы (user_id) дублируются для каждого элемента массива. Столбец-массив (tags) разворачивается в скалярный столбец (tag).
LEFT ARRAY JOIN: сохранение строк с пустыми массивами
Обычный ARRAY JOIN отбрасывает строки, где массив пуст ([]). Если пользователь не имеет тегов — он исчезнет из результата.
-- Строки с пустым tags[] исчезают:
SELECT user_id, tag
FROM events
ARRAY JOIN tags AS tag;
-- user_id=2, tags=[] -- НЕТ в результате
-- LEFT ARRAY JOIN сохраняет строки с пустыми массивами:
SELECT user_id, tag
FROM events
LEFT ARRAY JOIN tags AS tag;
-- user_id=2, tag='' -- пустая строка (default значение типа)
LEFT ARRAY JOIN аналогичен LEFT JOIN: строки без совпадений (пустой массив) сохраняются с default-значениями типа элемента массива ('' для String, 0 для UInt).
arrayJoin() — inline-разворачивание
Функция arrayJoin() разворачивает массив прямо в SELECT, без отдельного ARRAY JOIN clause:
-- Эквивалентно ARRAY JOIN tags AS tag:
SELECT user_id, arrayJoin(tags) AS tag
FROM events;
arrayJoin() и ARRAY JOIN — не полностью идентичны. arrayJoin() применяется на уровне выражения в SELECT, а ARRAY JOIN — на уровне FROM. При использовании нескольких arrayJoin() в одном SELECT ClickHouse создаёт декартово произведение. С ARRAY JOIN clause можно развернуть несколько массивов параллельно.
-- arrayJoin() с выражением:
SELECT
user_id,
arrayJoin(arrayMap(x -> x * 2, scores)) AS doubled_score
FROM events;
Параллельное разворачивание нескольких массивов
ARRAY JOIN может разворачивать несколько массивов одновременно. Массивы должны иметь одинаковую длину в каждой строке:
-- tags = ['news', 'sport'], scores = [10, 20]
SELECT user_id, tag, score
FROM events
ARRAY JOIN
tags AS tag,
scores AS score;
| user_id | tag | score |
|---|---|---|
| 1 | news | 10 |
| 1 | sport | 20 |
Элементы объединяются по позиции: первый элемент tags с первым элементом scores.
Если массивы имеют разную длину, ClickHouse дополнит короткий массив default-значениями. Это часто приводит к неожиданным результатам. Всегда проверяйте длины массивов:
SELECT user_id, length(tags), length(scores)
FROM events
WHERE length(tags) != length(scores);Практические примеры
Анализ тегов
-- Топ-10 самых популярных тегов
SELECT tag, count() AS cnt
FROM articles
ARRAY JOIN tags AS tag
GROUP BY tag
ORDER BY cnt DESC
LIMIT 10;
Разворачивание свойств событий
-- Nested-подобная структура: ключи и значения в парных массивах
-- prop_keys = ['device', 'browser', 'os']
-- prop_values = ['mobile', 'chrome', 'android']
SELECT
event_id,
key,
value
FROM events
ARRAY JOIN
prop_keys AS key,
prop_values AS value;
Фильтрация по элементам массива
-- Все пользователи с тегом 'premium'
SELECT DISTINCT user_id
FROM events
ARRAY JOIN tags AS tag
WHERE tag = 'premium';
Для простой проверки наличия элемента в массиве используйте has(tags, ‘premium’) — это быстрее, чем ARRAY JOIN с фильтрацией. ARRAY JOIN нужен, когда необходимо работать с каждым элементом отдельно (агрегация, JOIN с другой таблицей).
Производительность
ARRAY JOIN создаёт новые строки — это умножение данных. Массив из 100 элементов в 1 миллионе строк — 100 миллионов строк после ARRAY JOIN.
-- Проверка размера массивов перед ARRAY JOIN:
SELECT
avg(length(tags)) AS avg_len,
max(length(tags)) AS max_len,
quantile(0.99)(length(tags)) AS p99_len
FROM events;
Если P99 длины массива больше 1000 — рассмотрите фильтрацию до ARRAY JOIN или предварительную агрегацию. Массивы с тысячами элементов могут привести к значительному потреблению памяти.
Ключевые выводы
- ARRAY JOIN разворачивает массив в строки: 1 строка с N элементами = N строк.
- LEFT ARRAY JOIN сохраняет строки с пустыми массивами (default-значения типа).
- arrayJoin() — inline-версия в SELECT, но создаёт декартово произведение при нескольких вызовах.
- Парные массивы разворачиваются параллельно по позиции; длины должны совпадать.
- Производительность: ARRAY JOIN умножает количество строк — проверяйте длины массивов перед использованием.