Типы данных в ClickHouse напрямую влияют на скорость чтения, размер таблицы и удобство запросов. В аналитической базе нельзя бездумно хранить все как String: дата должна быть датой, деньги — Decimal, небольшие справочники — LowCardinality, а Nullable стоит включать только там, где действительно нужен отдельный смысл NULL

- Почему типы так важны
- Числовые типы
- Строки и LowCardinality
- Дата и время
- Nullable: включать осторожно
- JSON, Array, Tuple и Nested
- Пример хорошей схемы событий
- Как посмотреть типы в таблице
- Шпаргалка выбора типов
- Как мигрировать от сырых строк к нормальным типам
- Что еще влияет на выбор типов
- Когда можно оставить поле строкой?
- Что проверить перед созданием таблицы?
- Когда JSON и Array лучше вынести в отдельную тему?
Почему типы так важны
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 хранит словарь значений и компактные ссылки на него. Это особенно полезно для измерений в аналитике
Дата и время
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
Пример хорошей схемы событий
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 demo.pageviews;
SELECT name, type
FROM system.columns
WHERE database = 'demo' AND table = 'pageviews';
Шпаргалка выбора типов
| Данные | Рекомендуемый тип |
|---|---|
| id пользователя | UInt64 |
| тип события | LowCardinality(String) |
| день отчета | Date |
| время события | DateTime или DateTime64 |
| стоимость заказа | Decimal(12, 2) |
| HTTP-статус | UInt16 |
| теги | Array(String) |
| динамические свойства события | JSON, если структура реально меняется |
Как мигрировать от сырых строк к нормальным типам
В реальных проектах данные часто приходят из CSV, логов или внешнего API, где все выглядит как строка. Не обязательно сразу строить идеальную модель. Практичный путь — создать staging-таблицу для сырой загрузки, проверить качество данных, а затем переложить их в typed-таблицу через INSERT SELECT
CREATE TABLE demo.raw_events
(
event_time String,
user_id String,
event_type String,
amount String
)
ENGINE = MergeTree
ORDER BY tuple();
INSERT INTO demo.events
SELECT
toDate(parseDateTimeBestEffort(event_time)) AS event_date,
parseDateTimeBestEffort(event_time) AS event_time,
toUInt64(user_id) AS user_id,
event_type,
toDecimal64(amount, 2) AS amount
FROM demo.raw_events;
Так проще отлавливать мусорные значения: пустые даты, нечисловые id, неожиданные суммы. После стабилизации пайплайна staging-таблица может остаться как буфер или исчезнуть, если загрузчик сразу пишет в правильные типы
Что еще влияет на выбор типов
Когда можно оставить поле строкой?
Строка нормальна для URL, внешнего идентификатора или сырого payload, который редко участвует в фильтрах. Но дата, статус, сумма и тип события почти всегда выигрывают от нормального типа; это особенно заметно, когда вы начнете писать отчеты с функциями ClickHouse
Что проверить перед созданием таблицы?
Посмотрите не только пример данных, но и будущие запросы: где будут WHERE, GROUP BY, JOIN, какие поля попадут в ORDER BY. Если этот этап пропустить, придется возвращаться к уроку CREATE TABLE в ClickHouse уже после ошибок
Когда JSON и Array лучше вынести в отдельную тему?
Когда внутри payload появляются массивы тегов, динамические свойства события или вложенные атрибуты. Для этого есть отдельный разбор JSON, Array и ARRAY JOIN в ClickHouse: там проще увидеть, где тип помогает, а где усложняет запросы



