SELECT day,
revenue,
LAG(revenue) OVER (ORDER BY day) AS prev_revenue
FROM daily_revenue;`LAG` is the easiest way to compare the current row to the previous row.
Compare rows to prior or next rows with LAG and LEAD for change detection, retention, and trend analysis.
Reach backward or forward without a self join.
SELECT day,
revenue,
LAG(revenue) OVER (ORDER BY day) AS prev_revenue
FROM daily_revenue;`LAG` is the easiest way to compare the current row to the previous row.
SELECT day,
revenue,
LEAD(revenue) OVER (ORDER BY day) AS next_revenue
FROM daily_revenue;Use `LEAD` for forward-looking comparisons or interval construction.
SELECT day,
revenue,
LAG(revenue, 1, 0) OVER (ORDER BY day) AS prev_revenue_or_zero
FROM daily_revenue;The third argument is the default when an offset row does not exist.
SELECT month,
signups,
LAG(signups, 2) OVER (ORDER BY month) AS signups_2_months_ago
FROM monthly_signups;Offsets larger than 1 are useful for month-over-month or quarter-over-quarter comparisons.
Practical delta and retention examples.
SELECT day,
revenue,
revenue - LAG(revenue) OVER (ORDER BY day) AS revenue_change
FROM daily_revenue;A simple difference often matters more than the raw value.
SELECT day,
revenue,
ROUND(
100.0 * (revenue - LAG(revenue) OVER (ORDER BY day))
/ NULLIF(LAG(revenue) OVER (ORDER BY day), 0),
2
) AS pct_change
FROM daily_revenue;Use `NULLIF` to avoid division by zero.
SELECT user_id,
event_time,
event_time - LAG(event_time) OVER (
PARTITION BY user_id
ORDER BY event_time
) AS time_since_prev
FROM user_events;The exact subtraction syntax varies by dialect, but the window pattern stays the same.
SELECT customer_id,
billing_month,
status,
LEAD(status) OVER (
PARTITION BY customer_id
ORDER BY billing_month
) AS next_status
FROM subscription_history;Useful in churn modeling, handoff workflows, and lifecycle analysis.