Transaction Overview
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
- Asked in
- Meta
Problem
The finance team needs two numbers for the board slide: how many users have made at least one purchase, and the average transaction amount across all transactions.
The executive snapshot. Users, products, revenue.
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 |
Expected output
| unique_purchasers | avg_transaction_amount |
|---|---|
| 15 | 688.9572000000001 |
Worked solution and explanation
This is a grain question dressed up as a board-slide request. The two numbers come from the same table but at different grains: purchasers are counted per person, while the average is taken per transaction row. Anyone gets AVG(total_amount) right. The trap is the first number. Every row in transactions carries a user_id, so COUNT(user_id) looks like it counts users, but it counts purchase lines. On this data that gives 200 instead of 15. On the production table each buyer fans out to about twenty transactions, so the slide would claim twenty times the real customer base while the average beside it looks perfectly sane.
Read the grain of each number
Step 1: Count people, not purchase lines
user_id repeats on every transaction a person makes: each of the 15 buyers here sits on 12 to 14 rows. COUNT(DISTINCT user_id) collapses that fan-out, so a buyer with fourteen purchases counts once. A user who never bought has no row in transactions at all, so 'at least one purchase' needs no extra filter: appearing in the table is the condition.
Step 2: Average every transaction row
The second number is per transaction, so AVG(total_amount) runs over all 200 rows as recorded, including the -45.0 refund and the 0.0 voided sale. Averaging per-user subtotals first would weigh a one-time buyer the same as a heavy one, which answers a different question.
Step 3: Return both in one pass
Neither number is broken down by anything, so both aggregates sit in one SELECT with no GROUP BY. The result is one scan, one row, and two columns named for the slide.
SELECT
COUNT(DISTINCT user_id) AS unique_purchasers,
AVG(total_amount) AS avg_transaction_amount
FROM transactions- Counts purchase lines
COUNT(user_id)orCOUNT(*)returns 200 here, one per transaction. Nothing errors, and the correct average next to it makes the row look trustworthy.- Counts buyers
COUNT(DISTINCT user_id)returns 15: each buyer once, no matter how many times they bought.
Common follow-up questions
- What if you needed the median instead of the average? (Tests knowledge of `PERCENTILE_CONT` or a manual approach, since a median is not a streaming aggregate.)
- How would you add the total transaction count alongside these metrics? (Add `COUNT(*)` to the same `SELECT`; it sits at the row grain, like the average.)
- What if some `total_amount` values were NULL? (`AVG` skips NULL amounts while `COUNT(DISTINCT user_id)` still counts that buyer, so the two numbers would describe slightly different populations.)