Pre-aggregations
Materialized rollups that speed up queries, and the baseless models built entirely on them.
A pre-aggregation is a materialized rollup of a semantic model's measures at a chosen time grain and a fixed set of dimensions. When a query is fully covered by a pre-aggregation, the layer routes to the smaller rollup table instead of scanning the full base table — cutting scan cost and latency. Anything not covered falls back to the base table automatically.
Two artifacts, committed together
A pre-aggregation is two files:
- The
PreAggYAML — the routing declaration, atsundial/semantic_models/<base_model>/pre_aggs/<name>.yaml. - A dbt model (
.sql+.yml) — which physically builds the rollup table in your warehouse, under the dbt project that owns the base model's source table.
Neither alone is useful: the YAML with no table has nothing to route to; the table with no YAML is never queried. The Data Modeling Agent authors both.
PreAgg YAML
apiVersion: context-engine/v1
kind: PreAgg
metadata:
id: 9a8b7c6d-0000-0000-0000-000000000002
name: orders_daily
spec:
base_model_id: 7d4c1e2f-1a3b-4c8e-9f10-2a3b4c5d6e7f # the base model's metadata.id
materialized_asset_qualified_name: ANALYTICS.PUBLIC.ORDERS_DAILY
backs_measures: [order_total, order_count]
dimensions: [customer_id, country]
time_grain: day
filters: []| Field | Required | Notes |
|---|---|---|
base_model_id | Yes | The metadata.id of the base semantic model this rolls up (a UUID, never a name). |
materialized_asset_qualified_name | Yes | Three-part warehouse name of the rollup table the dbt model builds (not the base table). |
backs_measures | Yes (≥ 1) | Measure names covered by the rollup. Aggregate measures only — calculated measures are never eligible. |
dimensions | No | Dimension names kept in the rollup. Empty rolls up to the time grain only. |
time_grain | Yes | The grain the rollup is materialized at: day, week, month, quarter, or year. |
filters | No | SQL predicates baked into the rollup (e.g. "country = 'US'"). |
Routing rules
A query is served by a pre-aggregation only when all hold:
- Dimension coverage — every dimension the query groups by or filters on is in the pre-agg's
dimensions(or pinned by one of itsfilters). - Grain compatibility — the query's grain is at or coarser than the pre-agg's
time_grain(a monthly query can use a daily rollup; a daily query cannot use a monthly one). - Filter carriage — any predicate baked into the pre-agg's
filtersmust also be present in the query; baked-in filters are not inferred. - Active — the rollup table has materialized and been picked up.
If no single pre-aggregation covers the request, a model with a base table falls back to it silently.
count_distinct and rolling-window measures are not re-aggregatable across grains (summing daily distinct counts ≠ the weekly distinct count). Author one pre-aggregation per grain you intend to query.
Baseless models
A baseless semantic model omits asset_qualified_name — it has no base table and is served entirely by pre-aggregations. This is a valid, intentional design for models backed only by rollups.
- Every dimension must be covered by at least one active pre-aggregation on the model (collectively — different dimensions may live in different rollups). A dimension no pre-agg carries can never be queried, and a sync that leaves one uncovered is rejected.
- There is no fallback. A query whose measures, dimensions, and grain aren't covered by a single active pre-aggregation returns a "no base table" error. Author pre-aggregations for every grain your users will query.
- A
must_be_grouped_or_filtereddimension must appear in a pre-agg'sdimensionsor be pinned in itsfilters.
Still have questions?