The Ones Who Carry Us
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
Problem
Which products have brought in at least one hundred dollars across every sale in the transactions log, and how much revenue and how many units has each sold? The general manager reviewing product performance wants them listed by catalog product name, biggest earner first.
Every quarter, a few names hold up the whole line. Name them.
Tables
products
Columns: product_id INTEGER, product_name TEXT, category TEXT, price REAL, rating REAL, in_stock INTEGER
| product_id | product_name | category | price | rating | in_stock |
|---|---|---|---|---|---|
| 1001 | Basic Device 1X | Books | 17.52 | 2.7 | 1 |
| 1050 | Deluxe Bundle 2X | Clothing | 25.05 | 4.4 | 1 |
| 1099 | Pro Unit 3X | Home & Kitchen | 32.58 | 2.1 | 1 |
| 1148 | Ultra Tool 4X | Collectibles | 40.11 | 3.8 | 1 |
| 1197 | Essential Set 5X | Toys | 47.64 | 1.5 | 1 |
| 1246 | Classic System 6X | Beauty | 0 | 3.2 | 1 |
| 1295 | Smart Gadget 7X | Automotive | 62.7 | 4.9 | 0 |
| 1344 | Eco Kit 8X | Garden | 70.23 | 2.6 | 1 |
| 1393 | Turbo Pack 9X | Music | 77.76 | 4.3 | 0 |
| 1442 | Premium Widget 10X | Electronics | 85.29 | 2 | 1 |
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
| product_name | total_revenue | units_sold |
|---|---|---|
| Premium Widget 100X | 2713.98 | 2 |
| Eco Kit 98X | 2660.1 | 8 |
| Smart Gadget 97X | 2633.16 | 6 |
| Classic System 96X | 2606.22 | 4 |
| Essential Set 95X | 2579.28 | 2 |
Worked solution and explanation
What this problem really is
Strip away the performance-review framing and this is a labeled aggregation with a threshold. You sum revenue per product, attach the product's name so the output is readable, and keep only the products whose total clears one hundred dollars. Anyone can write the join. The tell is where the threshold goes: a filter on the summed revenue has to run after the rows are grouped, which means HAVING, not WHERE. Put the sum in WHERE and the query errors. Filter individual sales of one hundred dollars or more instead of the product's total and you quietly report the wrong leaderboard.
Break down the requirements
Step 1: Join products to transactions
The join lines up each sale with its catalog row on the shared product_id. That is what lets the summed revenue sit next to a human-readable product_name in the output. Sales with no product recorded have nothing to name, so the inner join drops them.
Step 2: Aggregate by p.product_name
GROUP BY p.product_name collapses every sale of a product into one row, and SUM(t.total_amount) plus SUM(t.quantity) compute the revenue and units for that product. Refunds and voided sales are summed as recorded.
Step 3: Filter groups with HAVING
The one hundred dollar cut is a condition on the summed revenue, so it belongs in HAVING. WHERE cannot see the sum because it is evaluated before grouping happens.
Step 4: Sort the final output
ORDER BY total_revenue DESC puts the biggest earners on top, matching how the general manager reads the review from best to worst.
The solution
SELECT p.product_name, SUM(t.total_amount) AS total_revenue, SUM(t.quantity) AS units_sold
FROM products p
JOIN transactions t ON p.product_id = t.product_id
GROUP BY p.product_name
HAVING SUM(t.total_amount) >= 100
ORDER BY total_revenue DESCCommon follow-up questions
- Some `transactions.product_id` values are NULL, and the inner join drops those sales entirely. Is excluding that revenue correct, or should the business see an 'unattributed' bucket? (Tests whether the candidate notices that an inner join silently discards rows with a null join key.)
- With millions of transactions, how would an index on `transactions.product_id` change the join and aggregation plan compared with a full scan? (Probes how indexing the join and grouping columns changes the execution plan at scale.)
- If new transactions keep arriving, how would you maintain per-product revenue totals incrementally instead of re-aggregating the whole table each run? (Tests understanding of incremental aggregation patterns for late-arriving data.)