Trend Spotter

A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
medium
Seniority
L4

Problem

Every row of transactions is one purchase, stamped with the user who made it and its transaction_date. Alongside each purchase's user, amount and date, show what the same user spent on the purchase just before it in time, left empty when there is none.

What did they spend last time? Context changes everything.

Tables

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 200 rows
user_idtotal_amounttransaction_dateprev_amount
1001222.292026-01-07NULL
100616.142026-01-181222.29
100414.092026-03-03616.14
1001020.242026-03-20414.09
100414.092026-04-031020.24

Worked solution and explanation

This is a per-user look-back dressed up as a feature request. Everyone hears 'previous amount' and reaches for a look-back. What separates candidates is the two scopes on it: the look-back must stay inside one user, and 'previous' must mean previous in transaction_date, not in transaction_id. Miss the first and each buyer's first purchase quietly inherits the last amount of whoever sorts before them instead of staying empty. Miss the second and you pair purchases with the wrong neighbors, because on this table ids and dates do not move together.

Getting there

Step 1: Scope the look-back to one user

Every row stays (200 in, 200 out), so this is not a collapse into one row per user. It is a per-row lookup into the same user's history. PARTITION BY user_id is the fence: without it, the row before a buyer's first purchase belongs to somebody else.

Step 2: Order each history by transaction_date

It is tempting to treat transaction_id as time. Here it is not. User 100's original purchases have ids 2005, 3010 and 4015, but their dates run July, April, January. The backfilled rows carry higher ids still, yet land in earlier years. Only ORDER BY transaction_date inside the window yields the purchase that really came just before.

Step 3: Leave the first purchase empty

LAG returns NULL when nothing precedes the row in its partition, which is exactly the empty first row the preview shows. Resist wrapping it in COALESCE(..., 0). A zero reads as a real purchase, and this table already holds a voided sale recorded as 0.0, so the model could no longer tell 'no history' from 'spent nothing'.

Look back one purchase within each user
SELECT
    user_id,
    total_amount,
    transaction_date,
    LAG(total_amount) OVER (
        PARTITION BY user_id
        ORDER BY transaction_date
    ) AS prev_amount
FROM transactions
Sorted by user, but not fenced by user
SELECT
    user_id,
    total_amount,
    transaction_date,
    LAG(total_amount) OVER (
        ORDER BY user_id, transaction_date
    ) AS prev_amount
FROM transactions

Right on 186 of 200 rows: every buyer's first purchase after the very first picks up the previous buyer's last amount.

Common follow-up questions