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:

Pre-aggregations

Add a rollup over the per-window measures to accelerate these queries. growth_window is a switch dimension, so it does not have to be included: pre-aggregation matching applies the selected value over the rollup scan. Leaving it out also keeps the rollup small — including it would multiply the rows by every window in the set. List the per-window measures, not the four case measures: the case measures are multi-stage and dispatch to the per-window ones, and it’s those that the rollup has to name to be matched. In the JavaScript model, reference them through CUBE[...] rather than by name as strings. Cube resolves member references by recording them as they’re accessed, so a string never registers as one — the rollup compiles with an empty measure list and silently matches nothing.
Query the view exactly as above. When querying through the REST (JSON) API, provide the date range via timeDimensions rather than an inDateRange filter — a filter is matched as a generic dimension filter, which requires the time dimension to be listed in the rollup’s dimensions.
  • 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.