CoreTrail

LAG, LEAD & period comparisons

Compare values in sequence without assuming dates are complete.

CorePostgreSQL3 min read
On this page

LAG accesses a previous row’s value. LEAD accesses a following row’s value. They select a value at an offset; they do not aggregate a frame.

SELECT sale_date, revenue,
       LAG(revenue) OVER (ORDER BY sale_date) AS previous_revenue,
       LEAD(revenue) OVER (ORDER BY sale_date) AS next_revenue,
       LAG(revenue, 2) OVER (ORDER BY sale_date) AS two_rows_back
FROM daily_sales
ORDER BY sale_date;

In PostgreSQL, changing the frame does not restrict which row LAG or LEAD accesses. Their default offset is one. A third argument supplies a fallback when that offset lies outside the partition; it does not replace an existing row’s null value.

Month-over-month growth

WITH monthly AS (
    SELECT DATE_TRUNC('month', order_ts)::date AS month,
           SUM(amount) AS revenue
    FROM orders
    WHERE status = 'paid'
    GROUP BY DATE_TRUNC('month', order_ts)::date
), compared AS (
    SELECT month, revenue,
           LAG(month) OVER (ORDER BY month) AS previous_month,
           LAG(revenue) OVER (ORDER BY month) AS previous_revenue
    FROM monthly
)
SELECT month, revenue,
       CASE
           WHEN month = (previous_month + INTERVAL '1 month')::date
           THEN ROUND(
               100.0 * (revenue - previous_revenue)
               / NULLIF(previous_revenue, 0), 2
           )
       END AS growth_pct
FROM compared
ORDER BY month;

The adjacency check avoids calling January-to-March change “month-over-month” when February is missing. If missing months mean zero revenue, construct a complete calendar first.

References

Type a concept, keyword, or function.