Back to Cube

Calculating period-over-period changes

docs/content/product/data-modeling/recipes/period-over-period.mdx

1.6.444.4 KB
Original Source

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.
<WarningBox>

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

</WarningBox>

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:

<CodeTabs>
yaml
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
javascript
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`
    }
  }
})
</CodeTabs>

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:

json
{
  "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:

<Screenshot src="https://lgo0ecceic.ucarecd.net/2f97cb29-903c-41ff-99e3-295535d2844f/"/>