SQL · medium

How would you build a month-over-month growth view in SQL for a dashboard?

Asked in BI Developer interviews, in the SQL round.

Short answer

Aggregate to month, then LAG the metric over ordered months: (this_month - LAG(metric) OVER (ORDER BY month)) / LAG(metric) OVER (ORDER BY month). Guard against divide-by-zero, and join to a month spine so a missing month reads as a gap rather than being silently skipped.

Sample data

These tables are loaded into an in-browser engine on this page, so you can run your own answer against them. No account needed.

orders

order_idorder_dateamount
12026-01-08120
22026-01-2180
32026-02-03150
42026-02-1790
52026-02-2560
62026-03-05200
72026-03-19110
82026-04-0295

How to answer it

Definition

Aggregate the metric to a month grain, then compare each month with the one before it using LAG. The result is one row per month with the previous value, the absolute change and the percentage change.

Example

WITH monthly AS (
  SELECT DATE_TRUNC('month', order_date) AS month,
         SUM(amount) AS revenue
  FROM   orders
  GROUP BY 1
)
SELECT month,
       revenue,
       LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
       revenue - LAG(revenue) OVER (ORDER BY month) AS change,
       ROUND(100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
             / NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 1) AS pct_change
FROM   monthly
ORDER BY month;

Output

month      | revenue | prev_revenue | change | pct_change
-----------+---------+--------------+--------+------------
2025-01-01 |  412000 |         NULL |   NULL |       NULL
2025-02-01 |  448500 |       412000 |  36500 |        8.9
2025-03-01 |  431200 |       448500 | -17300 |       -3.9

The three traps

Key points

FAQ

The 30-second answer

Aggregate to one row per month, then use LAG over the ordered months to bring the previous month alongside, and compute the difference and the percentage change from those two columns.
Three things I would guard. NULLIF on the denominator, because a month with zero revenue would otherwise divide by zero. A month spine, because LAG gives the previous row rather than the previous month, so a month with no data makes every later comparison wrong without raising an error. And the current month is incomplete, so I would exclude it or mark it, otherwise the dashboard shows a crash every first of the month.

In the interview

Practice this for real