DEFAULT, MATERIALIZED, ALIAS, EPHEMERAL
ClickHouse поддерживает четыре типа выражений столбцов, которые позволяют автоматически вычислять значения при INSERT или SELECT. Каждый тип имеет свой баланс между хранением, вычислениями и гибкостью.
Сравнительная таблица
DEFAULT: значение по умолчанию
Самый простой и гибкий тип. Если INSERT не предоставляет значение, используется выражение. Если предоставляет — переданное значение имеет приоритет.
CREATE TABLE logs (
timestamp DateTime,
level String DEFAULT 'INFO',
message String
) ENGINE = MergeTree()
ORDER BY timestamp
-- INSERT без level → level = 'INFO'
INSERT INTO logs (timestamp, message) VALUES (now(), 'Server started')
-- INSERT с level → level = 'ERROR'
INSERT INTO logs VALUES (now(), 'ERROR', 'Disk full')
MATERIALIZED: предвычисление при INSERT
MATERIALIZED столбец всегда вычисляется из выражения. Даже если INSERT передаёт значение, оно игнорируется. Хранится на диске, но не появляется в SELECT *.
Типичный use case: предвычисленная дата для PARTITION BY:
CREATE TABLE events (
timestamp DateTime,
date Date MATERIALIZED toDate(timestamp),
user_id UInt64,
data String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (user_id, timestamp)
Столбец date вычисляется из timestamp при каждом INSERT. Это экономит вычисления при чтении: вместо toDate(timestamp) в каждом запросе ClickHouse читает готовое значение из .bin файла.
-- SELECT * НЕ покажет date
SELECT * FROM events LIMIT 1
-- timestamp, user_id, data (без date)
-- Нужно указать явно
SELECT timestamp, date, user_id FROM events LIMIT 1
-- timestamp, date, user_id
ALIAS: виртуальный столбец
ALIAS не хранится на диске. Значение вычисляется при каждом SELECT. Это экономит хранение, но тратит CPU на каждый запрос.
Идеален для значений, которые зависят от текущего момента:
CREATE TABLE users (
user_id UInt64,
created_at DateTime,
age_days UInt32 ALIAS dateDiff('day', created_at, now()),
name String
) ENGINE = MergeTree()
ORDER BY user_id
Столбец age_days всегда возвращает актуальное число дней с момента регистрации. Он не занимает места на диске и не устаревает, потому что вычисляется заново при каждом запросе.
-- Нулевой расход диска
SELECT user_id, age_days FROM users WHERE user_id = 42
-- age_days вычисляется из created_at и now() в момент запроса
EPHEMERAL: входные данные для других столбцов
EPHEMERAL — самый экзотический тип. Столбец принимает значение при INSERT, передаёт его другим столбцам через их выражения, и затем уничтожается. Не хранится, не читается.
Паттерн: передать сырой JSON, извлечь поля:
CREATE TABLE parsed_events (
raw_json String EPHEMERAL,
name String DEFAULT JSONExtractString(raw_json, 'name'),
age UInt8 DEFAULT JSONExtractUInt(raw_json, 'age'),
city String DEFAULT JSONExtractString(raw_json, 'city')
) ENGINE = MergeTree()
ORDER BY name
-- INSERT передаёт raw_json → поля name, age, city извлекаются → raw_json отбрасывается
INSERT INTO parsed_events (raw_json)
VALUES ('{"name": "Alice", "age": 30, "city": "Moscow"}')
-- На диске хранятся только name, age, city. raw_json нигде нет.
SELECT * FROM parsed_events
-- name='Alice', age=30, city='Moscow'
Преимущество: клиент передаёт один blob, ClickHouse парсит его один раз при INSERT, хранит только нужные поля. JSON не занимает место на диске.
Ключевые выводы
- DEFAULT — значение по умолчанию. Хранится на диске, видно в SELECT *, можно переопределить при INSERT. Самый простой и распространённый тип.
- MATERIALIZED — предвычисление. Хранится на диске, но НЕ видно в SELECT * и НЕ переопределяемо. Экономит CPU при чтении за счёт хранения.
- ALIAS — виртуальный столбец. НЕ хранится на диске, вычисляется при каждом SELECT. Экономит диск за счёт CPU. Идеален для “живых” значений (возраст, текущая разница).
- EPHEMERAL — входной столбец. Принимает данные при INSERT, передаёт другим столбцам, затем уничтожается. Паттерн: парсинг JSON/сырых данных в типизированные поля.
- Баланс хранение vs вычисление: MATERIALIZED хранит и не считает при SELECT. ALIAS не хранит и считает при каждом SELECT. Выбирайте в зависимости от частоты чтения vs объёма данных.