В чём разница между типами данных Array, Nested и Map в ClickHouse?
Array — одномерный массив одного типа; Nested — синтаксический сахар над несколькими параллельными массивами; Map — ключ-значение с O(n) доступом. Выбор зависит от семантики данных и паттерна чтения.
Array, Nested и Map в ClickHouse
ClickHouse поддерживает три основных составных типа для хранения повторяющихся структур в одной строке. Они похожи по назначению, но сильно различаются по реализации и производительности.
Array(T)
Хранит произвольное число элементов одного типа. Это первоклассный тип: он хранится в одном файле .bin и поддерживает богатый набор функций:
CREATE TABLE events (
user_id UInt64,
tags Array(String),
scores Array(Float32)
) ENGINE = MergeTree()
ORDER BY user_id;
INSERT INTO events VALUES (1, ['ml', 'python', 'data'], [0.9, 0.7, 0.85]);
-- Работа с элементами
SELECT
user_id,
tags[1] AS first_tag, -- 1-based index
has(tags, 'python') AS has_python,
arrayFilter(x -> x > 0.8, scores) AS high_scores,
arrayMap(x -> x * 100, scores) AS pct_scores,
length(tags) AS tag_count
FROM events;
-- ARRAY JOIN разворачивает массив в строки
SELECT user_id, tag
FROM events
ARRAY JOIN tags AS tag;
Nested
Nested — синтаксический сахар: объявление нескольких колонок как «вложенной таблицы». На диске каждая колонка вложенной структуры хранится отдельным файлом (как самостоятельный Array), но ClickHouse гарантирует, что их длины совпадают и предоставляет удобный синтаксис обращения.
CREATE TABLE orders (
order_id UInt64,
items Nested(
product_id UInt32,
quantity UInt16,
price Decimal(10, 2)
)
) ENGINE = MergeTree()
ORDER BY order_id;
-- Вставка: каждая "колонка" Nested — отдельный массив
INSERT INTO orders VALUES (42, [101, 202], [3, 1], [9.99, 49.99]);
-- Обращение через точку
SELECT
order_id,
items.product_id,
items.quantity,
arraySum(items.price) AS total
FROM orders;
-- ARRAY JOIN по Nested-колонкам (join все колонки синхронно)
SELECT order_id, pid, qty, prc
FROM orders
ARRAY JOIN
items.product_id AS pid,
items.quantity AS qty,
items.price AS prc;
Map(K, V)
Map(String, UInt64) хранит набор пар ключ-значение. Внутри реализован как два параллельных массива (ключей и значений). Доступ по ключу — O(n), никакого хеш-индекса нет.
CREATE TABLE page_views (
session_id UInt64,
properties Map(String, String)
) ENGINE = MergeTree()
ORDER BY session_id;
INSERT INTO page_views VALUES
(1, {'browser': 'Chrome', 'os': 'Linux', 'country': 'RU'});
-- Обращение по ключу
SELECT
session_id,
properties['browser'] AS browser,
mapKeys(properties) AS keys,
mapValues(properties) AS vals,
mapContains(properties, 'os') AS has_os
FROM page_views;
-- Фильтрация по значению Map
SELECT count()
FROM page_views
WHERE properties['country'] = 'RU';
Сравнительная таблица
- Array: один тип элементов, богатые функции (
arrayMap,arrayFilter,arraySum), лучше всего для однородных наборов значений. - Nested: несколько связанных массивов одной длины, семантика «таблица внутри строки», удобен для нормализованных данных (позиции заказа, события сессии).
- Map: произвольные ключи (динамическая схема), O(n) доступ, подходит для properties/labels когда набор ключей заранее неизвестен.
Подводные камни
- Nested — это НЕ JOIN: при
ARRAY JOIN items.product_id, items.priceнужно перечислять все нужные колонки явно, иначе другие колонки Nested не синхронизируются. - Map — O(n) поиск: для горячих запросов с фильтрацией по ключу Map работает медленнее отдельной колонки; добавьте
BLOOM_FILTERили замените на реальную колонку. - Array индексируется с 1:
arr[0]в ClickHouse вернёт дефолтное значение, а не первый элемент — частая ошибка для разработчиков, привыкших к 0-based индексам. - Nested не поддерживает DEFAULT и ALTER ADD COLUMN: нельзя добавить новую колонку в существующий Nested-тип без пересоздания таблицы.
- Map(String, String) vs LowCardinality: ключи Map хранятся как полные строки; если ключи повторяются, используйте
Map(LowCardinality(String), String)для экономии памяти. - ARRAY JOIN создаёт декартово произведение: если джойнить два независимых массива (
ARRAY JOIN a, ARRAY JOIN b), результат будет декартовым произведением — используйте ARRAY JOIN обоих массивов в одном JOIN. - Глубина вложенности:
Array(Array(T))возможен, но операции с ним ограничены; не все функции поддерживают многомерные массивы.
Common mistakes
- Объяснять array, nested и map как OLTP-механику row-store базы вместо аналитической колоночной модели ClickHouse.
- Путать primary key ClickHouse с уникальным constraint из PostgreSQL или MySQL.
- Игнорировать parts, merges, ORDER BY, sparse index и стоимость маленьких вставок.
- Предлагать синтаксис или транзакционное поведение, которого в ClickHouse нет.
What the interviewer is testing
- Кандидат объясняет array, nested и map через реальный механизм ClickHouse, а не общими словами.
- Приводит корректный SQL или диагностический запрос для этой СУБД.
- Называет ограничения, версионные отличия или эксплуатационные последствия.
- Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.