Aggregating views (AggregatingMergeTree)¶
Overview¶
An aggregating view is a coherent triad:
- An AggregatingMergeTree target table whose columns have
AggregateFunction(...)types derived from aggregate expressions. - A materialized view that uses
<func>State(...)combinators in its SELECT,TOthe target. - The source table (referenced, not created; it must already exist).
Because the target column types and the MV combinators both derive from the
same single list of AggExpr, they are guaranteed consistent; the
correspondence that is manual and drift-prone in the string-SELECT form is
here derived and safe.
Declaring aggregating views¶
Use AggregatingView as the base class, aggregating_view() in Meta:
from sqlalchemy import func
from dbwarden.databases.clickhouse import AggregatingView, CHViewMeta, aggregating_view, agg
class EventsHourly(AggregatingView):
__tablename__ = "events_hourly"
class Meta(CHViewMeta):
ch = aggregating_view(
source=Event,
group_by=[func.toStartOfHour(Event.event_time).label("hour")],
aggregates=[
agg.sum(Event.amount).as_("total_amount"),
agg.count().as_("event_count"),
],
order_by=["hour"],
partition_by="toYYYYMM(hour)",
)
This generates:
- The target table
events_hourlywithAggregatingMergeTreeengine and columnshour DateTime,total_amount AggregateFunction(sum, Float64),event_count AggregateFunction(count). - A materialized view
events_hourly_mv TO events_hourlywhose SELECT usessumState,countState; automatically derived from theAggExprlist. - The MV reads FROM
Event.
The source parameter¶
source may be:
- A model class (preferred); its
__tablename__is resolved at spec construction time. - A string class name (forward reference); resolved at discovery time when all models are loaded.
- A bare table name string; used as-is.
When you pass a model class, rename safety is automatic: if the source table is renamed, regeneration picks up the new name.
Aggregate expressions¶
Every aggregate must have an alias (.as_(name)):
aggregates=[
agg.count().as_("event_count"),
agg.sum("amount", "Float64").as_("total_amount"),
agg.uniq("user_id").as_("unique_users"),
]
Supported aggregate functions include sum, count, min, max, avg,
uniq, uniq_exact, any, any_last, groupArray, groupUniqArray,
quantile, and the raw escape hatch for combinators not enumerated above.
Configuring the target table¶
The AggregatingViewSpec is a frozen dataclass. Configure it via
aggregating_view() keyword arguments:
| Parameter | Description |
|---|---|
source |
Source model class or table name |
group_by |
GROUP BY keys: ColumnElement or string |
aggregates |
AggExpr list (each with .as_()) |
order_by |
ORDER BY for the target |
partition_by |
Optional PARTITION BY |
ttl |
Optional TTL expression(s) |
settings |
Optional engine SETTINGS |
Full example¶
from sqlalchemy import func, String
from dbwarden.databases.clickhouse import (
AggregatingView, CHViewMeta, aggregating_view, agg,
)
class EventStats(AggregatingView):
__tablename__ = "event_stats"
class Meta(CHViewMeta):
ch = aggregating_view(
source=PageView,
group_by=[
PageView.url.label("url"),
func.toDate(PageView.viewed_at).label("day"),
],
aggregates=[
agg.count().as_("views"),
agg.uniq(PageView.session_id).as_("unique_sessions"),
agg.sum(PageView.duration).as_("total_duration"),
],
order_by=["url", "day"],
partition_by="toYYYYMM(day)",
ttl="day + INTERVAL 90 DAY DELETE",
)
Populating an aggregating view¶
Use the populate() helper from data_ops to generate an INSERT ... SELECT
DataOp that backfills the target table:
from dbwarden.databases.clickhouse import data_ops, AggregatingView
pop = data_ops.populate(EventStats.Meta.ch)
This produces a DataOp whose forward SQL is equivalent to:
INSERT INTO event_stats
SELECT
url,
toDate(viewed_at) AS day,
countState() AS views,
uniqState(session_id) AS unique_sessions,
sumState(duration) AS total_duration
FROM page_view
GROUP BY url, toDate(viewed_at)
Discoverability¶
AggregatingView subclasses are automatically registered in
ChView._ch_view_registry and discovered by ch_view_tables_from_models().
They contribute both the aggregating target model and the materialized view to
the model list.