Now: Getting KeyTips ready for release

SQL · 29 Oct 2026

LAG() doesn't know a month is missing

Month-over-month SQL quietly compares the wrong periods when a month has no rows. Build a month spine first.

This is the classic month-over-month query:

SELECT
  month,
  revenue,
  revenue - LAG(revenue) OVER (ORDER BY month) AS mom_change
FROM monthly_revenue;

LAG() looks back one row, not one month. If March has no rows, April gets compared with February, and nothing in the output warns you.

The fix: a month spine

Generate every month first, then join your data onto it. This example uses Postgres syntax:

WITH spine AS (
  SELECT generate_series(
    DATE '2026-01-01', DATE '2026-12-01', INTERVAL '1 month'
  )::date AS month
),
filled AS (
  SELECT s.month, COALESCE(r.revenue, 0) AS revenue
  FROM spine s
  LEFT JOIN monthly_revenue r ON r.month = s.month
)
SELECT
  month,
  revenue,
  revenue - LAG(revenue) OVER (ORDER BY month) AS mom_change
FROM filled;

If you report by product or entity, cross join the spine with that dimension and add PARTITION BY to the window.

One judgment call: COALESCE(..., 0) says “no rows means zero.” If it could mean “data hasn’t loaded yet”, leave the NULL in place so the gap stays visible.

← All tips