Skip to main content

Use case

Sometimes you want a measure to behave dynamically: its rolling window — and the prior-period shift you compare it against — should be chosen by the data consumer at query time rather than fixed in the data model. A common example is an embedded dashboard with a “window” dropdown (R3 / R6 / R9 / R12) where picking a value should change the query, not the data model. The window and the shift can’t be passed as query parameters. Both rolling_window and time_shift are properties of a measure definition, resolved when the model compiles — and a member must mean the same thing in every query, otherwise caching, pre-aggregation matching, and governance break. The trick is to move the choice into the query instead. A switch dimension holds the set of allowed windows and acts as the query-time parameter, and case measures dispatch to the matching rolling logic based on the selected value. Consumers only ever touch a small, fixed set of members, so those members stay well-defined and cacheable.

Data modeling

Say you have monthly gross_sales and want trailing 3-, 6-, 9-, and 12-month totals, each compared to the immediately preceding window of the same length. The model has four parts:
  • A growth_window switch dimension whose values are the selectable windows. This is the query-time parameter.
  • A rolling_window measure per window (the current period).
  • A time_shift measure per window that shifts the current-period measure back by the window’s length (the prior period).
  • Four case measuresgross_sales_current, gross_sales_prior, gross_sales_change, and gross_sales_growth_percentage — that dispatch on growth_window. Consumers query only these four, regardless of the selected window.
The per-window measures are near-identical, so Jinja generates them from a single list. Adding a window (say, R18) is a one-token change to windows, not a new measure by hand.
switch dimensions and case measures are powered by Tesseract, the next-generation data modeling engine. In versions before v1.7.0, it was not enabled by default.
Two requirements make case measures work:
  • Every case measure needs an else branch. It provides the value when the selected switch value matches no when clause.
  • Always include the switch dimension (growth_window) in the query. The case measures — and the calculated measures built on top of them — need it to dispatch. To pin a single window, add a filter on it (see below); don’t rely on the filter alone.
To give consumers a sensible default window, expose the cube through a view with a default_filters entry on growth_window. The unless clause releases the default as soon as the consumer filters on growth_window explicitly, so a query with no window filter gets the default, and a query that picks a window gets that one:

Result

Query the view through the SQL API, selecting growth_window and the four consumer measures. Wrap measures in MEASURE() and provide a date range for the rolling windows. With no filter on growth_window, the default_filters entry applies the default window (3m):
Filtering on growth_window — what a “window” dropdown does — selects that window:
Filtering on all values returns every window side by side, e.g. to render a comparison:
  • If you need a single fixed window rather than a consumer-selectable one, see Active users (DAU, WAU, MAU) (fixed rolling_window measures) and Period-over-period changes (a fixed time_shift comparison). This recipe generalizes both, making the window and shift selectable at query time.
  • Passing dynamic parameters in a query also lets a consumer choose something at query time, but the choice there is a data value (e.g. a city) injected into a calculation — not a measure behavior (the window length) as it is here.
  • Generating the data model dynamically generates a family of members from a list at model-build time; the consumer then picks by choosing which member to query, rather than passing a query-time value.