Repeat Purchases Within a Week

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
L5
Asked in
Vanguard

Problem

Which customers made two purchases within a week of each other during 2026, going by the transactions log? The retention team's repeat-buyer list needs each of those user IDs once.

They bought again within seven days.

Tables

transactions

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

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 13 rows
user_id
197
294
391
488
585

Worked solution and explanation

This is a per-customer pair search dressed up as a loyalty report. Every customer in transactions bought at least six times in 2026, so 'bought more than once' is not the question. The question is whether two of those purchases sit within seven days of each other. Count rows per user_id, or forget the day gap, and you hand back all 15 customers. The real answer is 13: 1264 and 1361 bought six times each this year, but never closer than 11 days apart.

Building the pairs

Step 1: Pair each purchase with the same customer's other purchases

Self-join transactions as earlier and later on user_id. Each row of the join is one candidate pair of purchases by one customer, which is exactly the thing the question is about.

Step 2: Put every pair in date order

later.transaction_date >= earlier.transaction_date fixes which row comes first, so the gap is never negative and no ABS is needed. The transaction_id <> check stops a row from pairing with itself, which would give every buyer a zero-day gap.

Step 3: Bound the year on earlier only

earlier.transaction_date >= '2026-01-01' is enough: the later purchase falls on or after the earlier one, and the log stops at today, so both land in 2026. A second bound on later would be redundant.

Step 4: Measure the gap in days, not characters

transaction_date is text. julianday() turns each side into a day number, so the subtraction is a real day count: exactly seven days apart passes <= 7, and same-day pairs are zero apart.

Step 5: Collapse pairs to customers

Three purchases in one week give three pairs. DISTINCT on earlier.user_id returns each customer once.

Customers with two 2026 purchases within a week
SELECT DISTINCT earlier.user_id
FROM transactions AS earlier
JOIN transactions AS later
  ON later.user_id = earlier.user_id
 AND later.transaction_id <> earlier.transaction_id
 AND later.transaction_date >= earlier.transaction_date
WHERE earlier.transaction_date >= '2026-01-01'
  AND julianday(later.transaction_date) - julianday(earlier.transaction_date) <= 7
Unordered pair with an absolute gap
Joining on t1.transaction_id < t2.transaction_id and testing ABS(...) <= 7 works, but the year bound then has to sit on both sides, because either row could be the earlier purchase.
Date-ordered pair
Joining on later.transaction_date >= earlier.transaction_date fixes which row is first, so one bound on earlier covers the whole pair and the gap needs no ABS.

Common follow-up questions