Repeat Buyers Across Halves

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
Asked in
Amazon

Problem

With 2026 only partway through its second half, a retention analyst treats a shopper in transactions as holding steady when their purchases since July number at least half of what they bought from January through June. Return those users with each half's purchase count as a whole number.

First half buyer. Second half buyer. Same person.

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 8 rows
user_idfirst_half_purchasessecond_half_purchases
10084
68243
77943
87643
116742

Worked solution and explanation

What this problem really is

This is a per-user comparison of two windows dressed up as retention analysis. The real skill: give every user two independent tallies inside one year, then judge the later tally against the earlier one for that same user. Anyone can count a user's rows. The trap is reaching for a fixed bar (four before July, two after) or measuring the post-July count against the whole year. On this data every active user clears a small fixed bar, so that list is just everyone, and measuring against the year doubles the bar and throws out the shoppers who actually held their pace.


Build it step by step

Step 1: Scope to 2026 first

transaction_date is text and spans several years. Pull the year out with strftime('%Y', ...) and CAST it to INTEGER before comparing to 2026: a text year never equals a number, so skipping the cast silently returns nothing. Doing this in WHERE means prior-year rows never reach the counts.

Step 2: Split each user into two counts

Same move for the month: CAST(strftime('%m', transaction_date) AS INTEGER). Then COUNT(CASE WHEN month <= 6 THEN 1 END) is the first half and COUNT(CASE WHEN month > 6 THEN 1 END) is the second. June lands in the first half, July in the second.

Step 3: One row per user_id

GROUP BY user_id collapses each user's year into one row, and the two counts ride along in the SELECT so the analyst sees how close each shopper sits to the line.

Step 4: Hold the back half to the front half

HAVING runs after grouping, so it can see both counts. Keep the user when twice the post-July count reaches the January-through-June count. Multiplying the second half by two keeps the comparison in integers, so no division and no rounding question.


The solution

Shoppers holding half their first-half pace in 2026
SELECT
    user_id,
    COUNT(CASE WHEN CAST(strftime('%m', transaction_date) AS INTEGER) <= 6 THEN 1 END) AS first_half_purchases,
    COUNT(CASE WHEN CAST(strftime('%m', transaction_date) AS INTEGER) > 6 THEN 1 END) AS second_half_purchases
FROM transactions
WHERE CAST(strftime('%Y', transaction_date) AS INTEGER) = 2026
GROUP BY user_id
HAVING 2 * COUNT(CASE WHEN CAST(strftime('%m', transaction_date) AS INTEGER) > 6 THEN 1 END)
    >= COUNT(CASE WHEN CAST(strftime('%m', transaction_date) AS INTEGER) <= 6 THEN 1 END)
Measured against the year
2 * second_half >= COUNT(*) quietly means the second half must at least match the first. A shopper with six purchases before July and three after held exactly half their pace and still gets dropped.
Measured against the first half
Comparing 2 * second_half_purchases to first_half_purchases sets each user's bar from their own spring. Heavy spring buyers need more summer activity; light ones need less.

Common follow-up questions