Purchase Log
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
- L3
- Asked in
- Tata Consultancy Services
Problem
A purchase only makes our audit log if it came in above fifty dollars, and every such purchase gets its own line. For each of those rows in transactions, show the buyer's username from users next to the amount spent and the purchase date.
Names on receipts, not just IDs.
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
| username | total_amount | transaction_date |
|---|---|---|
| arjun | 50.4 | 2026-04-04 |
| ava99 | 63.87 | 2026-05-05 |
| anika | 90.81 | 2026-07-07 |
| aiden | 104.28 | 2026-08-08 |
| aria_b | 117.75 | 2026-09-09 |
Worked solution and explanation
What this really is
This is a lookup join dressed up as an audit log. Everything the log needs already sits in transactions except the one column that makes it readable: the buyer's name lives only in users. Anyone can write WHERE total_amount > 50. What separates candidates is noticing that user_id is not a username and reaching across to users for it. Skip the join and you hand the auditors a column of opaque ids. Read the bar as >= 50 and a purchase of exactly fifty dollars leaks into a report meant to hold only purchases above it.
Building it
Step 1: Fix the grain: one row per purchase
The log lists purchases, so every row of transactions that clears the bar is a row of output. A buyer with a dozen qualifying purchases appears a dozen times. There is nothing to collapse, so no GROUP BY and no DISTINCT.
Step 2: Fetch the name from users
Inner-join users to transactions on user_id. Each transaction has exactly one buyer and user_id is unique in users, so the join attaches a name to each purchase without multiplying rows. Every buyer on this data has a users row, so no purchase falls out either.
Step 3: Apply the threshold strictly
total_amount > 50 keeps the 50.40 purchase and would turn away one at exactly 50.00. The same predicate also clears out the voided 0.00 sale and the -45.00 refund, which sit below the bar under any reading.
SELECT u.username, t.total_amount, t.transaction_date
FROM users u
JOIN transactions t ON u.user_id = t.user_id
WHERE t.total_amount > 50- Single-table shortcut
SELECT user_id, total_amount, transaction_date FROM transactions WHERE total_amount > 50runs and even returns the right number of rows, but the first column holds numbers like 391 that nobody reading the audit can interpret.- With the lookup
- The join swaps that number for
username, so the same row reads 'arjun ' with its amount and date: the thing the log was asked for in the first place.
Common follow-up questions
- Some transactions now reference a `user_id` with no row in `users`, and the log must still show them. What changes? (Tests whether the candidate knows an inner join drops unmatched purchases and switches to a left join with a placeholder name.)
- The auditors want each buyer's total spend on purchases above fifty dollars instead of the line-by-line log. How does the query change? (Moves from row-level enrichment to a per-buyer aggregate at a new grain.)
- How would you limit the log to the last 90 days without hardcoding a date? (Tests date arithmetic relative to the current date on `transaction_date`.)