Skip to Content
DocumentationData modelingRecipesPeriod-over-period changes

Calculating period-over-period changes

Use case

Often, there’s a need to calculate a period-over-period change in a metric, e.g., week-over-week or month-over-month growth of clicks, orders, revenue, etc.

Data modeling

In Cube, calculating a period-over-period metric involves the following steps:

  • Define a multi-stage measure for the current period.
  • Define a time-shift measure that references the current period measure and shifts it to the previous period.
  • Define a calculated measure that references these measures and uses them in a calculation, e.g., divides or subtracts them.

Multi-stage calculations are powered by Tesseract, the next-generation data modeling engine . Tesseract is currently in preview. Use the CUBEJS_TESSERACT_SQL_PLANNER environment variable to enable it.

The following data model allows to calculate a month-over-month change of some value. current_month_sum is the base measure, previous_month_sum is a time-shift measure that shifts the current month data to the previous month, and the month_over_month_ratio measure divides their values:

cubes: - name: month_over_month sql: | SELECT 1 AS value, '2024-01-01'::TIMESTAMP AS date UNION ALL SELECT 2 AS value, '2024-01-01'::TIMESTAMP AS date UNION ALL SELECT 3 AS value, '2024-02-01'::TIMESTAMP AS date UNION ALL SELECT 4 AS value, '2024-02-01'::TIMESTAMP AS date UNION ALL SELECT 5 AS value, '2024-03-01'::TIMESTAMP AS date UNION ALL SELECT 6 AS value, '2024-03-01'::TIMESTAMP AS date UNION ALL SELECT 7 AS value, '2024-04-01'::TIMESTAMP AS date UNION ALL SELECT 8 AS value, '2024-04-01'::TIMESTAMP AS date dimensions: - name: date sql: date type: time measures: - name: current_month_sum sql: value type: sum - name: previous_month_sum multi_stage: true sql: "{current_month_sum}" type: number time_shift: - interval: 1 month type: prior - name: month_over_month_ratio multi_stage: true sql: "{current_month_sum} / NULLIF({previous_month_sum}, 0)" type: number
cube(`month_over_month`, { sql: ` SELECT 1 AS value, '2024-01-01'::TIMESTAMP AS date UNION ALL SELECT 2 AS value, '2024-01-01'::TIMESTAMP AS date UNION ALL SELECT 3 AS value, '2024-02-01'::TIMESTAMP AS date UNION ALL SELECT 4 AS value, '2024-02-01'::TIMESTAMP AS date UNION ALL SELECT 5 AS value, '2024-03-01'::TIMESTAMP AS date UNION ALL SELECT 6 AS value, '2024-03-01'::TIMESTAMP AS date UNION ALL SELECT 7 AS value, '2024-04-01'::TIMESTAMP AS date UNION ALL SELECT 8 AS value, '2024-04-01'::TIMESTAMP AS date `, dimensions: { date: { sql: `date`, type: `time` } }, measures: { current_month_sum: { sql: `value`, type: `sum` }, previous_month_sum: { multi_stage: true, sql: `${current_month_sum}`, type: `number`, time_shift: [{ interval: `1 month`, type: `prior` }] }, month_over_month_ratio: { multi_stage: true, sql: `${current_month_sum} / NULLIF(${previous_month_sum}, 0)`, type: `number` } } })

Result

Often, when calculating period-over-period changes, you would also use a query with a time dimension and granularity that matches the period, i.e., month for month-over-month calculations:

{ "timeDimensions": [ { "dimension": "month_over_month.date", "granularity": "month", "dateRange": ["2024-01-01", "2025-01-01"] } ], "measures": [ "month_over_month.current_month_sum", "month_over_month.previous_month_sum", "month_over_month.month_over_month_ratio" ] }

Here’s the result:

Was this page useful?