.agents/skills/clickhouse-best-practices/rules/query-mv-incremental.md
Impact: HIGH
Incremental MVs automatically apply the view's query to new data blocks at insert time. Results are written to a target table and partial results merge over time.
Incorrect (full aggregation on every query):
-- Full aggregation on every dashboard load
SELECT
event_type,
toStartOfHour(timestamp) as hour,
count() as events,
uniq(user_id) as unique_users
FROM events
WHERE timestamp >= now() - INTERVAL 7 DAY
GROUP BY event_type, hour;
-- Scans 7 days of data every time (billions of rows)
Correct (incremental MV with pre-aggregation):
-- Create target table for aggregated data
CREATE TABLE events_hourly (
event_type LowCardinality(String),
hour DateTime,
events AggregateFunction(count),
unique_users AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (event_type, hour);
-- Create materialized view to populate incrementally
CREATE MATERIALIZED VIEW events_hourly_mv TO events_hourly AS
SELECT
event_type,
toStartOfHour(timestamp) as hour,
countState() as events,
uniqState(user_id) as unique_users
FROM events
GROUP BY event_type, hour;
-- Query the pre-aggregated data
SELECT
event_type, hour,
countMerge(events) as events,
uniqMerge(unique_users) as unique_users
FROM events_hourly
WHERE hour >= now() - INTERVAL 7 DAY
GROUP BY event_type, hour;
-- Reads thousands of rows instead of billions
Key points:
-State functions in MV, -Merge functions in queryReference: Use Materialized Views