LAG and LEAD: Removing the Self-Join From Every Growth Report

Advanced 5 min read Updated 30 Jul 2026 UPA AI Partner

You are self-joining to compare this month against last. One function does it three times faster in half the code.

The problem

Calculate revenue growth against the previous month. There is no “previous month” column — you have to fetch it yourself.

GOCthangdoanh_thu01100021200390SAU LAGthangdoanh_thuthang_truoctang_%01100NULLNULL0212010020.00390120-25.0LAG

The pattern

SELECT
  month,
  revenue,
  LAG(revenue) OVER (ORDER BY month) AS prev_month,
  ROUND(100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
        / NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 1) AS growth_pct
FROM monthly_revenue;

LAG looks backwards, LEAD looks forwards. Both take two optional extra arguments:

LAG(col, 2)        -- go back 2 rows
LAG(col, 1, 0)     -- go back 1 row, default to 0

Common mistakes

1. Omitting ORDER BY inside OVER

-- Arbitrary result, different on every run
LAG(revenue) OVER ()

LAG is meaningless without an order. ORDER BY inside OVER is mandatory.

2. Omitting PARTITION BY across groups

-- WRONG: Hanoi's January reads Da Nang's December
LAG(revenue) OVER (ORDER BY month)

-- RIGHT
LAG(revenue) OVER (PARTITION BY city ORDER BY month)

This is the quietest bug here: numbers still appear, they are just wrong.

3. Not handling the first row

The first row has no predecessor, so LAG returns NULL, and dividing by NULL also gives NULL. Logically correct — but if the report needs a zero, use LAG(revenue, 1, 0).

4. Missing months

LAG takes the previous row in the result set, not “the previous calendar month”. If February has no data, March silently compares against January. Build a complete month range with a CROSS JOIN first.

The second use: gaps between purchases

SELECT
  customer_id,
  order_date,
  order_date - LAG(order_date) OVER (
    PARTITION BY customer_id ORDER BY order_date
  ) AS days_since_last
FROM orders;

From here you get the average purchase cycle and can spot customers whose gaps are widening — the foundation of every churn model.

Practice

  • LeetCode 197 — Rising Temperature
  • LeetCode 1907 — Count Salary Categories
  • LeetCode 1454 — Active Users
  • LeetCode 2701 — Consecutive Transactions with Increasing Amounts
  • HackerRank — Interviews

Key takeaways

  • LAG reads backwards, LEAD reads forwards.
  • ORDER BY inside OVER is mandatory.
  • Multiple groups always need PARTITION BY.
  • The first row always returns NULL — handle it with the third argument.
  • LAG reads the previous row, not the previous calendar period.

You have just learned LAG và LEAD

Ready to practise?

Work through LAG và LEAD exercises on real data, graded the moment you hit run.

Start practising →