Logo SQL Growth

Analyzing Customer Purchase Intervals with LAG()

by DataMarvin
19 hours ago
Views: 4
Illustrative Image

Analyzing Customer Purchase Intervals with LAG()

You already know how to rank rows with ROW_NUMBER().
But what if you want to compare a row against the previous one?

That's exactly what LAG() is for.


The Problem

You have an orders table. You want to know:

How many days passed between each customer's purchases?

This kind of question appears constantly in e-commerce:

  • Repurchase cycle analysis
  • Churn detection ("it's been 60 days since their last order")
  • Revenue trend ("did this customer buy more or less than last time?")

Sample Data

CREATE TABLE orders ( order_id INT, customer_id INT, order_date DATE, amount INT ); INSERT INTO orders VALUES (1, 101, '2024-01-05', 30000), (2, 101, '2024-01-20', 45000), (3, 101, '2024-02-10', 20000), (4, 102, '2024-01-08', 15000), (5, 102, '2024-02-01', 60000), (6, 102, '2024-02-25', 25000);

LAG() Syntax

LAG(column, offset, default) OVER (PARTITION BY ... ORDER BY ...)
ParameterMeaning
columnThe value you want to look back at
offsetHow many rows back (default: 1)
defaultWhat to return when there is no previous row

Step 1 — Bring in the Previous Order Date

SELECT customer_id, order_date, amount, LAG(order_date) OVER ( PARTITION BY customer_id ORDER BY order_date ) AS prev_order_date FROM orders;

Result:

customer_idorder_dateamountprev_order_date
1012024-01-0530000NULL
1012024-01-20450002024-01-05
1012024-02-10200002024-01-20
1022024-01-0815000NULL
1022024-02-01600002024-01-08
1022024-02-25250002024-02-01

PARTITION BY customer_id ensures each customer's history is independent.
The first purchase per customer returns NULL — there is no previous row.


Step 2 — Calculate the Interval

Wrap the result in a CTE and compute the gap.

WITH lagged AS ( SELECT customer_id, order_date, amount, LAG(order_date) OVER ( PARTITION BY customer_id ORDER BY order_date ) AS prev_order_date FROM orders ) SELECT customer_id, order_date, prev_order_date, DATEDIFF(order_date, prev_order_date) AS days_since_last_order FROM lagged;

Result:

customer_idorder_dateprev_order_datedays_since_last_order
1012024-01-05NULLNULL
1012024-01-202024-01-0515
1012024-02-102024-01-2021
1022024-01-08NULLNULL
1022024-02-012024-01-0824
1022024-02-252024-02-0124

Step 3 — Add Amount Change

You can call LAG() multiple times in one query.

WITH lagged AS ( SELECT customer_id, order_date, amount, LAG(order_date) OVER ( PARTITION BY customer_id ORDER BY order_date ) AS prev_order_date, LAG(amount) OVER ( PARTITION BY customer_id ORDER BY order_date ) AS prev_amount FROM orders ) SELECT customer_id, order_date, amount, DATEDIFF(order_date, prev_order_date) AS days_since_last_order, amount - prev_amount AS amount_change FROM lagged;

Result:

customer_idorder_dateamountdays_since_last_orderamount_change
1012024-01-0530000NULLNULL
1012024-01-204500015+15000
1012024-02-102000021-25000
1022024-01-0815000NULLNULL
1022024-02-016000024+45000
1022024-02-252500024-35000

What About LEAD()?

LEAD() is the mirror image — it looks forward instead of backward.

SELECT customer_id, order_date, LEAD(order_date) OVER ( PARTITION BY customer_id ORDER BY order_date ) AS next_order_date FROM orders;

Use LEAD() when you want to answer:

  • "When did this customer come back after this purchase?"
  • "Was this the customer's last order?"

LAG vs LEAD — Quick Reference

LAG()LEAD()
DirectionLooks backLooks forward
Typical usePrevious purchase, prior monthNext purchase, upcoming event
NULL appears onFirst row per partitionLast row per partition

Key Takeaways

  • LAG() pulls a value from a previous row within the same partition.
  • PARTITION BY keeps each customer's history separate — without it, rows bleed across customers.
  • The first row per partition always returns NULL; handle it with COALESCE() if needed.
  • LEAD() does the same thing in the opposite direction.
  • Combine with DATEDIFF() for interval analysis, or subtract amounts to track spend trends.

Practice Query

Find customers whose repurchase interval shortened compared to the gap before it.
(Hint: you'll need two levels of LAG() or a CTE with two passes.)

More

Based on Tags

Recent Popular

Most Popular

  • SQL SELECT 가이드

    SQL SELECT 기초부터 실전 활용까지

    Illustrative Image
  • 데이터 분석을 위한 SQL 입문 - 왜 SQL부터 시작해야 할까?

    SQL이 첫번째 언어여야 하는 이유

    Illustrative Image
  • SQL로 전환 여부 파악하기

    클릭은 있지만 전환은 없는 셀러 찾기

    Illustrative Image