ClickHouse
Типы данных ClickHouse: String, UInt, DateTime и JSON
Типы данных в ClickHouse напрямую влияют на скорость чтения, размер таблицы и удобство запросов. В аналитической базе нельзя бездумно хранить все как String: дата должна быть датой, деньги — Decimal, небольшие справочники — LowCardinality, а Nullable стоит включать только там, где действительно нужен отдельный смысл NULL
Почему типы так важны
ClickHouse хранит данные по колонкам. Когда значения в колонке одного типа и хорошо повторяются, они эффективно сжимаются и быстро читаются. Если вместо чисел, дат и статусов везде лежат строки, база теряет часть преимуществ: сложнее фильтровать, хуже сжатие, больше преобразований в запросах
Числовые типы
| Тип | Когда использовать |
|---|---|
UInt8, UInt16, UInt32, UInt64 |
Счетчики, id без отрицательных значений, статусы |
Int8…Int64 |
Числа, где возможны отрицательные значения |
Float32, Float64 |
Измерения, метрики, приблизительные значения |
Decimal |
Деньги и точные десятичные значения |
Не берите максимальный тип «на всякий случай». Если статус помещается в UInt8, не нужен UInt64. Чем точнее тип, тем лучше сжатие и меньше чтение
Строки и LowCardinality
String подходит для произвольных строк: URL, поисковый запрос, сообщение, внешний id. Но если значений немного и они повторяются, используйте LowCardinality(String). Примеры: страна, тип события, тариф, источник, статус
event_type LowCardinality(String),
country LowCardinality(String),
plan LowCardinality(String)
LowCardinality хранит словарь значений и компактные ссылки на него. Это особенно полезно для измерений в аналитике
Когда можно оставить поле строкой?
String подходит для URL, внешнего идентификатора или сырого значения, которое редко участвует в фильтрах. Для даты, суммы, статуса и типа события обычно полезнее выбрать специализированный тип
Дата и время
Date— день без времени. Подходит для партиций, отчетов по дням, календарных фильтров.Date32— расширенный диапазон дат.DateTime— дата и время с точностью до секунды.DateTime64— дата и время с долями секунды, например для логов и метрик.
event_date Date,
event_time DateTime,
request_time DateTime64(3)
Если точность миллисекунд не нужна, не ставьте DateTime64 по привычке. Более точный тип не всегда лучше: он может быть избыточным для отчетов по дням и часам
Nullable: включать осторожно
Nullable(T) нужен, когда отсутствие значения отличается от нуля, пустой строки или значения по умолчанию. Но Nullable добавляет служебные данные и усложняет некоторые операции. Если поле всегда известно или можно задать нормальное значение по умолчанию, Nullable не нужен
-- Уместно: дата первого платежа может быть неизвестна
first_payment_at Nullable(DateTime)
-- Часто не нужно: статус лучше хранить явным значением
status LowCardinality(String) DEFAULT 'unknown'
JSON, Array, Tuple и Nested
ClickHouse умеет работать с полуструктурированными данными, но выбор зависит от задачи. Если структура стабильная, лучше использовать обычные колонки. Если ключи действительно меняются от события к событию, можно рассмотреть тип JSON. Если у записи есть список тегов, подойдет Array(String)
tags Array(String),
payload JSON,
position Tuple(Float64, Float64)
Не путайте JSON как формат загрузки и JSON как тип колонки. Можно загружать строки в формате JSONEachRow, но это не значит, что каждая колонка должна быть типа JSON
Когда JSON и Array лучше вынести в отдельную тему?
Когда в данных появляются массивы тегов, динамические свойства или вложенные атрибуты. На обзорной странице достаточно выбрать семейство типов, а подробные запросы и ARRAY JOIN лучше разбирать отдельно
Что проверить перед созданием таблицы?
Посмотрите не только на пример данных, но и на будущие запросы: какие поля участвуют в
WHERE,GROUP BY,JOINиORDER BY. Подробный сценарий создания схемы вынесен в инструкцию как создать таблицу ClickHouse
Пример хорошей схемы событий
CREATE TABLE demo.pageviews
(
event_date Date,
event_time DateTime64(3),
user_id UInt64,
url String,
source LowCardinality(String),
status UInt16,
duration_ms UInt32,
revenue Decimal(12, 2),
tags Array(String),
payload JSON
)
ENGINE = MergeTree
ORDER BY (event_date, source, user_id);
Как посмотреть типы в таблице
DESCRIBE TABLE numbers(1);
В официальном Playground команда возвращает колонку number типа UInt64. Для своей схемы замените numbers(1) на имя таблицы
Шпаргалка выбора типов
| Данные | Рекомендуемый тип |
|---|---|
| id пользователя | UInt64 |
| тип события | LowCardinality(String) |
| день отчета | Date |
| время события | DateTime или DateTime64 |
| стоимость заказа | Decimal(12, 2) |
| HTTP-статус | UInt16 |
| теги | Array(String) |
| динамические свойства события | JSON, если структура реально меняется |
Как мигрировать от сырых строк к нормальным типам
В реальных проектах данные часто приходят из CSV, логов или внешнего API, где все выглядит как строка. Не обязательно сразу строить идеальную модель. Практичный путь — создать staging-таблицу для сырой загрузки, проверить качество данных, а затем переложить их в typed-таблицу через INSERT SELECT
SELECT
toDate(parseDateTimeBestEffort(event_time)) AS event_date,
parseDateTimeBestEffort(event_time) AS event_time_typed,
toUInt64(user_id) AS user_id_typed,
event_type,
CAST(amount AS Decimal(12, 2)) AS amount_typed
FROM values(
'event_time String, user_id String, event_type String, amount String',
('2026-07-20 12:34:56', '42', 'purchase', '19.95')
);
Проверенный результат: дата 2026-07-20, время 2026-07-20 12:34:56, пользователь 42 и сумма 19.95. На реальных данных сначала выявите пустые даты, нечисловые id и неожиданные суммы, а затем переносите строки в типизированную таблицу
Что еще влияет на выбор типов
Выбор заканчивается не названием типа, а проверкой на реальных запросах и данных. Зафиксируйте диапазоны чисел, долю пустых значений, число уникальных строк и необходимую точность времени. Затем сравните схему с фильтрами, группировками и ключом сортировки