Тип столбца определяет допустимые значения, способ хранения и операции над ними. В аналитической базе это влияет не только на правильность, но и на объём распаковки: миллиард значений UInt64 и UInt8 занимают разный объём до сжатия.
Числа по смыслу и диапазону
UInt — беззнаковое целое, Int — целое со знаком. Число в имени обозначает количество бит. Например, UInt8 хранит 0–255, UInt32 — 0–4 294 967 295. Для количества строк используем UInt64: отрицательное значение не имеет смысла, а объём может быть большим. Для коррекции-слагаемого, которое бывает отрицательным, нужен знаковый тип.
Размер целого типа выбирают по диапазону источника. Если длительность в миллисекундах может превышать предел UInt32, тип нужно расширить.
Float32 и Float64 хранят приближённые двоичные числа. Они удобны для измерений и статистики, но не гарантируют точное десятичное представление суммы денег. Decimal(P, S) хранит десятичное число с фиксированным масштабом: P — общее количество цифр, S — количество после точки.
SELECT
toDecimal64('12.30', 2) + toDecimal64('0.20', 2) AS amount,
toTypeName(amount) AS type;Результат суммы — 12.50. Строки в преобразовании задают десятичные значения без промежуточного Float. При умножении масштаб результата может вырасти; правила округления при делении задают явно.
Дата, время и часовой пояс
Date хранит календарную дату. DateTime — время с точностью до секунды. DateTime64(3, 'UTC') поддерживает миллисекунды; 3 задаёт количество десятичных разрядов дробной секунды.
SELECT
toDateTime('2026-01-01 09:00:00', 'UTC') AS finished,
toTimeZone(finished, 'Europe/Moscow') AS local_time,
toDate(finished) AS utc_day;Один момент отображается как 09:00 UTC и 12:00 в Москве. Часовой пояс является частью типа, а не отдельной меткой в каждой строке. Если источник передаёт времена без зоны, сначала нужно выяснить их смысл. Дневная группировка в UTC и локальной зоне может дать разные календарные дни.
Для дневных отчётов секунд может быть достаточно. В логах с высокой частотой событий может понадобиться DateTime64.
Nullable сохраняет отсутствие значения
Nullable(UInt32) допускает NULL в дополнение к обычным числам. ClickHouse хранит признак отсутствия отдельной маской, поэтому чтение и вычисления получают дополнительную работу.
Однако заменять неизвестную длительность на 0 ради скорости опасно: среднее начнёт учитывать выдуманный нулевой запуск. Агрегатные функции обычно пропускают NULL; count() считает строки, count(duration) — известные значения.
| measurement_id | duration |
|---|---|
| 1 | 100 |
| 2 | NULL |
| 3 | 200 |
Среднее известных длительностей равно 150. После подмены NULL на 0 получилось бы 100: в расчёт попало бы выдуманное нулевое измерение. Nullable сохраняет различие между неизвестным значением и нулём.
LowCardinality кодирует повторяющиеся значения
Кардинальность — число различных значений. У статуса может быть несколько вариантов, у категории — десятки, у уникального идентификатора — столько, сколько строк. Чем больше уникальных значений при том же числе строк, тем выше кардинальность.
LowCardinality(String) хранит словарь строк и коды, ссылающиеся на словарь. Вместо многократного хранения 'billing' можно хранить компактные коды. Некоторые вычисления тоже работают с этим представлением эффективнее.
LowCardinality удобен для статусов, категорий и повторяющихся названий. При почти уникальных значениях стоимость словаря возрастает; выбор проверяют на реальных данных и запросах.
Enum задаёт список допустимых значений в типе. LowCardinality такого списка не навязывает: появление нового источника не требует добавить его в Enum. Выбор зависит от того, нужна ли именно фиксированная категория.
Массивы, Map и JSON
Array(String) хранит список значений, Tuple — набор полей, Map(String, String) — пары ключ–значение. Массив удобен для меток запуска:
SELECT
['daily', 'backfill'] AS tags,
has(tags, 'backfill') AS is_backfill,
tags[1] AS first_tag;Индексы массивов в ClickHouse начинаются с 1. arrayJoin разворачивает массив в строки и может изменить число строк перед агрегацией.
Тип JSON описывает значение столбца, формат JSONEachRow — входные строки. Современный JSON может выделять пути в подколонки и полезен при меняющейся структуре. Известные обязательные поля проще хранить типизированными колонками. Для документа, который сохраняют и возвращают целиком, может хватить String.
toUInt32OrNull выделяет некорректный текст, после чего NULL обрабатывают явно. OrZero заменяет ошибку нулём и может скрыть дефект источника. Отдельно проверяют диапазон и смысл преобразованных значений.