Sundial
Semantic Layer

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:

  1. The PreAgg YAML — the routing declaration, at sundial/semantic_models/<base_model>/pre_aggs/<name>.yaml.
  2. 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: []
FieldRequiredNotes
base_model_idYesThe metadata.id of the base semantic model this rolls up (a UUID, never a name).
materialized_asset_qualified_nameYesThree-part warehouse name of the rollup table the dbt model builds (not the base table).
backs_measuresYes (≥ 1)Measure names covered by the rollup. Aggregate measures only — calculated measures are never eligible.
dimensionsNoDimension names kept in the rollup. Empty rolls up to the time grain only.
time_grainYesThe grain the rollup is materialized at: day, week, month, quarter, or year.
filtersNoSQL predicates baked into the rollup (e.g. "country = 'US'").

Routing rules

A query is served by a pre-aggregation only when all hold:

  1. Dimension coverage — every dimension the query groups by or filters on is in the pre-agg's dimensions (or pinned by one of its filters).
  2. 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).
  3. Filter carriage — any predicate baked into the pre-agg's filters must also be present in the query; baked-in filters are not inferred.
  4. 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_filtered dimension must appear in a pre-agg's dimensions or be pinned in its filters.

Still have questions?

On this page