Two Names on the Ledger
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
Finance is reconciling revenue for two customers, alice and aaron42, and wants to see how their spend stacks up across both accounts, one transaction at a time. From the earliest transaction ID to the latest, return each transaction's ID, the customer, its amount, and the combined revenue accumulated through it.
Two accounts. One ledger. Watch the spend stack up.
Tables
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 |
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 |
Expected output
| transaction_id | username | total_amount | running_total |
|---|---|---|---|
| 1067 | aaron42 | 23.46 | 23.46 |
| 2005 | alice | 212.04 | 235.5 |
| 2072 | aaron42 | 225.51 | 461.01 |
| 3010 | alice | 414.09 | 875.0999999999999 |
| 3077 | aaron42 | 427.56 | 1302.6599999999999 |
Worked solution and explanation
What this is really testing
Strip off the reconciliation story and this is a cumulative sum scoped to a filtered set of rows. The skill being probed: can you keep every transaction row visible while attaching a tally that grows down the ordered list? Anyone can sum revenue for two users. The trap is collapsing the detail: candidates reach for a grouped SUM, get one number per user, and lose the per-transaction rows the prompt explicitly asks for. The other failure is computing the tally over everybody and only then filtering to alice and aaron42, which leaves the accumulation contaminated by transactions that should never have been in scope.
Building it
Step 1: Join transactions to users
transactions stores only user_id, so you cannot filter on a name directly. An inner join to users on user_id brings username into reach and, being inner, quietly drops any named user who has no transactions.
Step 2: Scope to the two customers
WHERE u.username IN ('alice', 'aaron42') narrows the set first. Doing it here, not after the window, is the whole game: it guarantees the tally sums only these two users' spend.
Step 3: Accumulate without collapsing
SUM(t.total_amount) OVER (ORDER BY t.transaction_id) computes a per-row cumulative figure: each row carries the sum of its own amount plus every earlier transaction_id. Because it is a window aggregate, the original transaction rows survive untouched.
SELECT t.transaction_id,
u.username,
t.total_amount,
SUM(t.total_amount) OVER (ORDER BY t.transaction_id) AS running_total
FROM transactions t
INNER JOIN users u ON t.user_id = u.user_id
WHERE u.username IN ('alice', 'aaron42')
ORDER BY t.transaction_idFilter to the two users first, then accumulate over transaction_id.
- Grouped sum (wrong shape)
GROUP BY u.usernamewithSUM(total_amount)returns one row per user. The per-transaction detail and the row-by-row tally the prompt asks for are gone.- Window sum (correct)
SUM(...) OVER (ORDER BY transaction_id)keeps every transaction row and attaches the cumulative figure to each, which is exactly the requested output.
Common follow-up questions
- How would you change the query so each user gets their own independent running tally instead of one combined figure? (Tests whether the candidate knows `PARTITION BY u.username` resets the accumulation per user.)
- If two transactions shared the same `transaction_id`, how would the cumulative figure behave and how would you make it deterministic? (Tests understanding of window ordering ties and the default frame including peers.)
- At billions of rows, which step becomes the bottleneck and what index would you reach for first? (Tests ability to reason about filter selectivity and join cost on `transactions.user_id` at scale.)