Skip to main content
Availability: Pre-aggregates are a Beta feature available on Enterprise plans only.
Pre-aggregates let you define materialized summaries of your data directly in your dbt YAML. When a user runs a query in Lightdash, the system checks if the query can be answered from a pre-aggregate instead of querying your warehouse. If it matches, the query is served from the pre-computed results, making it significantly faster and reducing warehouse load. This is especially useful for dashboards with high traffic or expensive aggregations that don’t need real-time data. Any query that goes through the Lightdash semantic layer can hit a pre-aggregate — this includes the Lightdash app, the API, MCP, AI agents, the Embed SDK, and the React SDK. Watch this video walkthrough for an overview of how to get started with pre-aggregates:

Getting started

Define pre-aggregates in your dbt project and configure scheduling.

Monitoring and debugging

Track materialization status, debug query matching, and view hit/miss stats.

CLI audit

Inspect dashboard coverage from the terminal and gate CI on hit rates.

How it works

Pre-aggregates follow a four-step cycle:
  1. Define — You add a pre_aggregates block to your dbt model YAML, specifying which dimensions and metrics to include.
  2. Materialize — Lightdash runs the aggregation query against your warehouse and stores the results. This happens automatically on compile, on a cron schedule you define, or when you trigger it manually.
  3. Match — When a user runs a query, Lightdash checks if every requested dimension, metric, and filter is covered by a pre-aggregate.
  4. Serve — If a match is found, the query is served from the materialized data instead of hitting your warehouse.

Example

Suppose you have an orders table with thousands of rows, and you define a pre-aggregate with dimensions status and metrics total_amount (sum) and order_count (count), with a day granularity on order_date. Your warehouse data: Lightdash materializes this into a pre-aggregate: Now when a user queries “total amount by status, grouped by month”, Lightdash re-aggregates from the daily pre-aggregate instead of scanning the full table: This works because sum can be re-aggregated — summing daily sums gives the correct monthly sum.

Query matching

When a user runs a query, Lightdash checks whether a pre-aggregate can serve it first. A pre-aggregate matches when the query fits inside it on each axis:
  • Fields are available — every dimension, metric, and filter dimension in the query exists somewhere in the pre-aggregate.
  • Grain is reachable — if the query uses a time dimension, its granularity is equal or coarser than the pre-aggregate’s, so the rows can be rolled up. Month is a coarser grain than day. Day is a coarser grain than hour.
  • Scope is compatible — if the pre-aggregate defines its own filters, the query includes an equal or narrower filter, so the subset can be filtered from the pre-aggregate base.
  • Metrics re-aggregate cleanly — all metrics are supported types. No non-additive metrics (like count distinct, median, etc. which require recalculation per grouping) and nothing resolved at runtime (raw SQL table calculations, sql_filter metrics, or SQL dependent on Parameters and user attributes). These can’t be faithfully re-computed from stored rows.
A day pre-aggregate serves day, week, month, quarter, and year queries. A month pre-aggregate serves month, quarter, and year — but not day or week, since those need finer-grained data.
When multiple pre-aggregates match a query, Lightdash picks the smallest one (fewest dimensions, then fewest metrics as tiebreaker).

Filtered pre-aggregates

A pre-aggregate can define static filters so it materializes only a slice of the source data for a common query pattern, such as status = completed or a rolling order_date: inThePast 52 weeks window. A query then matches it only when it carries the same filter or a narrower one — the scope-compatibility rule above. See Filtered pre-aggregates for the definition syntax and a worked matching example.

Dimensions from joined tables

Pre-aggregates support dimensions from joined tables. Reference them by their full name (for example, customers.first_name) in the dimensions list.

Supported metric types

Pre-aggregates support metrics that can be re-aggregated from pre-computed results:
  • sum
  • count
  • min
  • max
  • average

Current limitations

Pre-aggregates support a narrower subset of the Lightdash semantic layer than regular warehouse queries.

Not supported

Pre-aggregates do not support:

SQL compatibility

sql_filter (and its alias sql_where) runs both at materialization time and at query time on top of the materialized data.
  • At materialization time, the filter is evaluated against your warehouse. If the SQL references Parameters or user attributes, the values injected come from the materialization context — you can pin this to a fixed identity or attribute set with materialization_role so the materialization captures the rows you need.
  • At query time, the same filter is re-applied against the materialized data, which is served by DuckDB. If the sql_filter SQL uses warehouse-specific syntax that DuckDB doesn’t understand, the query will fail to run against the pre-aggregate and fall back to the warehouse.

Metrics that can’t be pre-aggregated

Pre-aggregates do not support metric types that cannot be re-aggregated from pre-computed results. For example, consider count_distinct on a daily pre-aggregate. If the pre-aggregate stores “2 distinct customers on 2024-01-15” and “1 distinct customer on 2024-01-16”, you cannot sum those daily values to get the monthly distinct count, because the same customer can appear on multiple days. Re-aggregating gives 2 + 1 = 3, but the correct monthly answer is 2 (Alice, Bob). The pre-aggregate no longer knows which customers were counted. We’re investigating supporting count_distinct through approximation algorithms. Follow this issue for updates. For similar reasons, the following metric types are also not supported:
  • sum_distinct, average_distinct
  • median, percentile
  • percent_of_total, percent_of_previous
  • running_total
  • Custom SQL / post-calculation metrics (including many number metrics) — Follow this issue
  • number, string, date, timestamp, boolean
For metrics that can’t be pre-aggregated, consider using caching instead.

Pre-aggregates vs results caching

Pre-aggregates and results caching are independent systems that speed up queries in different ways, and they work best together: pre-aggregates serve matching queries from materialized summary tables — no warehouse hit, even on the first query — while results caching stores the exact result of any query shape after its first run. A query that hits a pre-aggregate can also have its result cached, layering the two. For the full comparison — a feature-by-feature table and guidance on when to use each — see Results caching vs pre-aggregates.

Getting started with pre-aggregates

Define pre-aggregates in your dbt YAML, configure scheduling, and start serving queries from materialized data.

Monitoring and debugging pre-aggregates

Track materialization status, understand why queries miss pre-aggregates, and manage refreshes.

Auditing pre-aggregates from the CLI

Use lightdash pre-aggregate-audit to inspect coverage, find gaps in your YAML, and gate CI on dashboard hit rates.