Before They Walk
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
- mid
- Asked in
- Allstate
Problem
Most of the revenue in transactions seems to come from a tiny sliver of customers, and the retention team wants a white-glove loyalty program for exactly those people before a competitor poaches them. Find the ten customers who have spent the most across every purchase they have ever made, showing each one's user ID, username from users, and total spend, biggest spenders first.
A few names carry the whole ledger. Reach them first, before someone else does.
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
transactions
Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT
| transaction_id | user_id | product_id | quantity | total_amount | transaction_date |
|---|---|---|---|---|---|
| 1067 | 197 | 1001 | 2 | 23.46 | 2026-02-02 |
| 1134 | 294 | 1050 | 3 | 36.93 | 2026-03-03 |
| 1201 | 391 | 1099 | 4 | 50.4 | 2026-04-04 |
| 1268 | 488 | 1148 | 5 | 63.87 | 2026-05-05 |
| 1335 | 585 | 1197 | 0 | 0 | 2026-06-06 |
| 1402 | 682 | 1246 | 2 | 90.81 | 2026-07-07 |
| 1469 | 779 | 1295 | 3 | 104.28 | 2026-08-08 |
| 1536 | 876 | 1344 | 4 | 117.75 | 2026-09-09 |
| 1603 | 973 | 1393 | 5 | -45 | 2026-01-10 |
| 1670 | 1070 | 1442 | 1 | 144.69 | 2026-02-11 |
Expected output
| user_id | username | lifetime_spend |
|---|---|---|
| 1070 | bjorn99 | 10511.76 |
| 973 | brian | 10146.96 |
| 876 | aria_b | 10134.6 |
| 779 | aiden | 9946.02 |
| 682 | anika | 9757.44 |
Worked solution and explanation
Why this problem exists in real interviews
This is the canonical revenue-concentration question, and it shows up on phone screens because it quietly checks four things at once: can you connect a fact table to a dimension table, can you collapse many rows per entity into one number, can you rank that number, and can you cut the list off at a useful size. The purchase facts live in transactions (one row per purchase, with total_amount and user_id), but the human identity lives in users (username keyed by user_id). A candidate has to recognize that total_amount must be summed per customer, then attach the name, then surface only the heaviest contributors. The decision that separates a clean answer from a messy one is grouping on user_id (the stable key) while still carrying username through for readability.
Break down the requirements
Step 1: Connect purchases to people
Each transaction only carries a user_id, but the team needs a name. Match every transaction row to its owner in users on user_id so the username travels alongside the dollars.
Step 2: Collapse to one total per customer
A customer has many purchases, so add up total_amount per user_id. Group on the user_id (and carry username along) and SUM the amounts into a single lifetime_spend figure per person.
Step 3: Rank and trim
Sort the per-customer totals from largest to smallest and keep only the first ten rows, since the loyalty program targets just the heaviest spenders.
The solution
SELECT u.user_id AS user_id, u.username AS username, SUM(t.total_amount) AS lifetime_spend FROM users u JOIN transactions t ON u.user_id = t.user_id GROUP BY u.user_id, u.username ORDER BY lifetime_spend DESC LIMIT 10;Common follow-up questions
- How would you also show each customer's number of separate purchases next to their total? (Tests whether the candidate can add COUNT alongside SUM in the same grouped query without disturbing the ranking.)
- If the team only cares about spend in the last 90 days, how does the query change and what happens to your partition scan? (Tests date filtering in the WHERE clause and awareness that a bounded date range lets partition pruning kick in at scale.)