✏️ Explanatory Question
Level: Hard — Essential for trend analysis; "compare a row to its neighbour" is a top analytics interview task.
month = month - 1 (ugly and slow). The interviewer wants the modern window-function way.
monthly_revenue| month | revenue |
|---|---|
| 2026-01 | 1000 |
| 2026-02 | 1200 |
| 2026-03 | 1100 |
| 2026-04 | 1600 |
n rows before the current row (default n = 1).n rows after the current row.NULL.
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
revenue - LAG(revenue) OVER (ORDER BY month) AS change_amount,
ROUND(
(revenue - LAG(revenue) OVER (ORDER BY month))
/ LAG(revenue) OVER (ORDER BY month) * 100, 2
) AS pct_change
FROM monthly_revenue
ORDER BY month;
| month | revenue | prev_revenue | change_amount | pct_change |
|---|---|---|---|---|
| 2026-01 | 1000 | NULL | NULL | NULL |
| 2026-02 | 1200 | 1000 | +200 | 20.00 |
| 2026-03 | 1100 | 1200 | -100 | -8.33 |
| 2026-04 | 1600 | 1100 | +500 | 45.45 |
The first row is NULL (no previous month) — correct behaviour.
SELECT
month,
revenue,
LAG(revenue, 1, 0) OVER (ORDER BY month) AS prev_revenue, -- default 0
ROUND(
(revenue - LAG(revenue) OVER (ORDER BY month))
/ NULLIF(LAG(revenue) OVER (ORDER BY month), 0) * 100, 2
) AS pct_change -- NULLIF avoids divide-by-zero if prev is 0
FROM monthly_revenue
ORDER BY month;
-- Flag rows where the status changed from the previous row (audit trails)
SELECT
log_time,
status,
LAG(status) OVER (PARTITION BY server_id ORDER BY log_time) AS prev_status,
CASE WHEN status <> LAG(status) OVER (PARTITION BY server_id ORDER BY log_time)
THEN 'CHANGED' ELSE 'same' END AS transition
FROM server_logs;
LAG/LEAD work on row position, are gap-tolerant, and compute in a single ordered pass — far faster and cleaner.
LAG(revenue, 12) to look back 12 rows — but only if every month is present. If months can be missing, join on an explicit date key instead, since LAG counts rows, not calendar gaps.