SQL LAG and LEAD Functions Explained
LAG looks back at the previous row and LEAD looks ahead to the next, in an order you choose. They replace awkward self joins for questions like "change since the last order", "days between visits" or "month-over-month growth".
Examples run in MySQL 8 and PostgreSQL · Updated
In short
LAG(col)returns the value from the previous row;LEAD(col)from the next.PARTITION BYkeeps the comparison inside each customer or group.- The first row of a partition has no previous row, so
LAGis NULL. - An optional default replaces that NULL:
LAG(col, 1, 0).
Previous and next rows
LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) means: for each order, take the amount of the same customer's previous order by date. The window never crosses into another customer.
A second argument looks further back: LAG(amount, 2) is two orders ago. A third argument sets a default instead of NULL.
Dates between rows
Gaps in days are written differently per database: DATEDIFF(order_date, LAG(order_date) OVER (…)) in MySQL, and plain subtraction order_date - LAG(order_date) OVER (…) in PostgreSQL. The examples below stay with amounts so they run unchanged in both.
The example tables
Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.
orders
| order_id | customer_id | order_date | amount |
|---|---|---|---|
| 101 | 1 | 2025-01-05 | 120 |
| 102 | 1 | 2025-01-20 | 80 |
| 103 | 2 | 2025-01-07 | 200 |
| 104 | 1 | 2025-02-02 | 150 |
| 105 | 3 | 2025-02-10 | 90 |
| 106 | 2 | 2025-02-25 | 60 |
| 107 | 3 | 2025-03-01 | 130 |
| 108 | 1 | 2025-03-03 | 40 |
Examples
Previous order amount
Each customer's first order has no previous order, so prev_amount is NULL.
SELECT customer_id, order_id, amount,
LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS prev_amount
FROM orders
ORDER BY customer_id, order_date;| customer_id | order_id | amount | prev_amount |
|---|---|---|---|
| 1 | 101 | 120 | NULL |
| 1 | 102 | 80 | 120 |
| 1 | 104 | 150 | 80 |
| 1 | 108 | 40 | 150 |
| 2 | 103 | 200 | NULL |
| 2 | 106 | 60 | 200 |
| 3 | 105 | 90 | NULL |
| 3 | 107 | 130 | 90 |
Change since last order, and the next order
Subtracting LAG gives the change; LEAD shows which order comes next.
SELECT customer_id, order_id, amount,
amount - LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS amount_change,
LEAD(order_id) OVER (PARTITION BY customer_id ORDER BY order_date) AS next_order
FROM orders
ORDER BY customer_id, order_date;| customer_id | order_id | amount | amount_change | next_order |
|---|---|---|---|---|
| 1 | 101 | 120 | NULL | 102 |
| 1 | 102 | 80 | -40 | 104 |
| 1 | 104 | 150 | 70 | 108 |
| 1 | 108 | 40 | -110 | NULL |
| 2 | 103 | 200 | NULL | 106 |
| 2 | 106 | 60 | -140 | NULL |
| 3 | 105 | 90 | NULL | 107 |
| 3 | 107 | 130 | 40 | NULL |
Common mistakes
- Forgetting PARTITION BY. Without it, the first order of customer 2 is compared with the last order of customer 1.
- Filtering on LAG in WHERE. Window functions are computed after
WHERE. ComputeLAGin a CTE, then filter in the outer query. - Ignoring the first row. Arithmetic with NULL gives NULL. Decide whether the first row should be dropped, kept as NULL or defaulted with
COALESCE.
Practise LAG and LEAD
Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.
Month-over-Month Revenue Growth
Compute monthly successful payment volume and its percentage change versus the prior month.
Current and Previous Subscription Plan
From a plan-change history, return each user's current plan and the one before it.
Ordered Purchase Funnel
Measure a three-step conversion funnel where the steps must happen in order.
Average Days Between Orders
Measure each repeat customer's average gap between consecutive orders.
Advocate Program Long-Call Growth
Measure month-over-month growth in the number of long support calls.
Drivers With Back-to-Back Drops
Flag drivers whose weekly completed trips fell by 30% or more two weeks in a row.
Want a full set? Work through the Window Functions study plan.
Questions people ask
- Are LAG and LEAD available in MySQL?
- Yes, since MySQL 8.0, with the same syntax as PostgreSQL.
- How do I compare with the same month last year?
- Use LAG(value, 12) over months ordered by date, as long as there is exactly one row per month.