Создание материализованных представлений, инкрементальная агрегация, live views, словари
Предварительная агрегация данных, инкрементальные вычисления, live views и словари
Материализованное представление (Materialized View) — объект БД, который автоматически обновляется при вставке данных в исходную таблицу и хранит результат запроса в физической таблице.
Отличие от обычных представлений:
-- Исходная таблица
CREATE TABLE events
(
event_time DateTime,
user_id UInt64,
event_type String,
value Decimal(10, 2)
)
ENGINE = MergeTree()
ORDER BY (event_time, user_id);
-- Материализованное представление
CREATE MATERIALIZED VIEW events_daily_mv
ENGINE = SummingMergeTree()
ORDER BY (date, event_type)
AS SELECT
toDate(event_time) AS date,
event_type,
count() AS events,
sum(value) AS total_value
FROM events
GROUP BY date, event_type;Важно: MV не имеет собственной таблицы для хранения — данные пишутся куда-то.
-- Таблица для хранения агрегатов
CREATE TABLE events_daily
(
date Date,
event_type String,
events UInt64,
total_value Decimal(12, 2)
)
ENGINE = SummingMergeTree()
ORDER BY (date, event_type);
-- MV с указанием таблицы назначения
CREATE MATERIALIZED VIEW events_daily_mv TO events_daily
AS SELECT
toDate(event_time) AS date,
event_type,
count() AS events,
sum(value) AS total_value
FROM events
GROUP BY date, event_type;Преимущества TO:
INSERT INTO events
↓
Триггер MV
↓
Выполнение SELECT из MV
↓
INSERT в таблицу назначения
Важно:
-- Вставка в исходную таблицу
INSERT INTO events (event_time, user_id, event_type, value)
VALUES
('2026-03-01 10:00:00', 1, 'click', 1.0),
('2026-03-01 11:00:00', 2, 'click', 1.5),
('2026-03-01 12:00:00', 1, 'view', 0.5);
-- Данные автоматически агрегируются в MV
SELECT * FROM events_daily;
-- Результат:
-- date | event_type | events | total_value
-- 2026-03-01 | click | 2 | 2.5
-- 2026-03-01 | view | 1 | 0.5CREATE TABLE hourly_stats
(
hour DateTime,
event_type LowCardinality(String),
events UInt64,
unique_users UInt64
)
ENGINE = SummingMergeTree()
ORDER BY (hour, event_type);
CREATE MATERIALIZED VIEW hourly_stats_mv TO hourly_stats
AS SELECT
toStartOfHour(event_time) AS hour,
event_type,
count() AS events,
uniq(user_id) AS unique_users
FROM events
GROUP BY hour, event_type;CREATE TABLE daily_stats
(
date Date,
event_type String,
uniq_users AggregateFunction(uniq, UInt64),
sum_value SimpleAggregateFunction(sum, Decimal(10, 2)),
max_value SimpleAggregateFunction(max, Decimal(10, 2))
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, event_type);
CREATE MATERIALIZED VIEW daily_stats_mv TO daily_stats
AS SELECT
toDate(event_time) AS date,
event_type,
uniqState(user_id) AS uniq_users,
sumState(value) AS sum_value,
maxState(value) AS max_value
FROM events
GROUP BY date, event_type;
-- Чтение с применением агрегации
SELECT
date,
event_type,
uniqMerge(uniq_users) AS unique_users,
sumMerge(sum_value) AS total_value,
maxMerge(max_value) AS max_value
FROM daily_stats
GROUP BY date, event_type;-- MV только для важных событий
CREATE TABLE important_events
(
event_time DateTime,
user_id UInt64,
event_type String
)
ENGINE = MergeTree()
ORDER BY (event_time, user_id);
CREATE MATERIALIZED VIEW important_events_mv TO important_events
AS SELECT
event_time,
user_id,
event_type
FROM events
WHERE event_type IN ('purchase', 'registration');
-- Теперь в important_events попадают только нужные события-- MV с обогащёнными данными
CREATE TABLE enriched_events
(
event_time DateTime,
user_id UInt64,
event_type String,
country String,
is_mobile UInt8
)
ENGINE = MergeTree()
ORDER BY (event_time, user_id);
CREATE MATERIALIZED VIEW enriched_events_mv TO enriched_events
AS SELECT
e.event_time,
e.user_id,
e.event_type,
dictGet('users_dict', 'country', e.user_id) AS country,
e.user_agent LIKE '%Mobile%' AS is_mobile
FROM events AS e;Live View — материализованное представление с поддержкой подписки на изменения.
CREATE LIVE VIEW events_live AS
SELECT
toDate(event_time) AS date,
count() AS events
FROM events
GROUP BY date;-- Подписка с получением изменений
WATCH events_live;
-- В другом соединении:
INSERT INTO events VALUES (...);
-- Первый клиент получит уведомление об измененииОграничения:
Словарь — структура данных для быстрого обогащения запросов.
CREATE DICTIONARY users_dict
(
user_id UInt64,
country String,
city String,
segment String
)
PRIMARY KEY user_id
SOURCE(CLICKHOUSE(
HOST 'localhost'
PORT 9000
USER 'default'
DB 'default'
TABLE 'users'
))
LAYOUT(HASHED())
LIFETIME(MIN 300 MAX 360);Параметры:
-- Получение значения
SELECT
user_id,
dictGet('users_dict', 'country', user_id) AS country
FROM events;
-- Проверка наличия
SELECT
user_id,
dictGetOrDefault('users_dict', 'country', user_id, 'Unknown') AS country
FROM events;
-- Множественные значения
SELECT
user_id,
dictGet('users_dict', 'country', user_id) AS country,
dictGet('users_dict', 'city', user_id) AS city
FROM events;1. Прямой (Direct):
LAYOUT(DIRECT())2. Хэш (Hashed):
LAYOUT(HASHED())3. Кэш (Cache):
LAYOUT(CACHE(SIZE 1000000))4. Range (Диапазоны):
LAYOUT(RANGE_HASHED())
LIFETIME(MIN 300 MAX 360)
RANGE(MIN start_date MAX end_date)-- ClickHouse таблица
SOURCE(CLICKHOUSE(...))
-- MySQL
SOURCE(MYSQL(
HOST 'mysql-host'
PORT 3306
USER 'user'
PASSWORD 'pass'
DB 'database'
TABLE 'users'
))
-- PostgreSQL
SOURCE(POSTGRES(...))
-- HTTP URL
SOURCE(HTTP(URL 'http://example.com/dict.csv'))
-- Файл
SOURCE(FILE(PATH '/dict.csv', FORMAT 'CSV'))
-- MongoDB
SOURCE(MONGODB(...))-- Список MV
SELECT name, database
FROM system.tables
WHERE engine LIKE '%MaterializedView%';
-- Определение MV
SHOW CREATE MATERIALIZED VIEW events_daily_mv;
-- Статус MV
SELECT
name,
is_attached,
metadata_modification_time
FROM system.tables
WHERE database = 'default'
AND engine LIKE '%MaterializedView%';-- Отключить MV (данные не будут писаться)
DETACH MATERIALIZED VIEW events_daily_mv;
-- Включить MV
ATTACH MATERIALIZED VIEW events_daily_mv;-- Удалить только MV (таблица назначения останется)
DROP MATERIALIZED VIEW events_daily_mv;
-- Удалить MV и таблицу назначения
DROP TABLE events_daily;
DROP MATERIALIZED VIEW events_daily_mv;-- Очистить таблицу назначения
TRUNCATE TABLE events_daily;
-- Пересоздать MV
DROP MATERIALIZED VIEW events_daily_mv;
CREATE MATERIALIZED VIEW events_daily_mv TO events_daily
AS SELECT ...;
-- Заполнить историческими данными
INSERT INTO events_daily
SELECT
toDate(event_time) AS date,
event_type,
count() AS events,
sum(value) AS total_value
FROM events
GROUP BY date, event_type;-- Хорошо: явная таблица
CREATE TABLE agg_table (...) ENGINE = SummingMergeTree();
CREATE MATERIALIZED VIEW mv TO agg_table AS SELECT ...;
-- Плохо: неявная таблица
CREATE MATERIALIZED VIEW mv AS SELECT ...;-- После создания MV заполните историей
INSERT INTO agg_table
SELECT ...
FROM source_table
WHERE date >= '2025-01-01';-- Хорошо: простая агрегация
CREATE MATERIALIZED VIEW mv TO target
AS SELECT date, count() FROM events GROUP BY date;
-- Плохо: сложные JOIN
CREATE MATERIALIZED VIEW mv TO target
AS SELECT e.*, u.country
FROM events e
JOIN users u ON e.user_id = u.id;-- Проверка отставания
SELECT
name,
metadata_modification_time,
toLastModification(name) AS last_update
FROM system.tables
WHERE engine LIKE '%MaterializedView%';Изучим управление данными: INSERT, UPDATE, DELETE, мутации, TTL.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.