The Crown Jewels
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
- L5
- Asked in
- FinThrive
Problem
Across our whole catalog and all of its sales history in transactions, we want the products that have brought in the most money. List the top 5 by total revenue with their names from products, biggest earner first.
Revenue crowns the winners. Who sold the most?
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, product_id INTEGER, total_amount REAL, user_id INTEGER, quantity INTEGER, 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 |
|---|---|
| Premium Widget 100X | 2713.98 |
| Eco Kit 98X | 2660.1 |
| Smart Gadget 97X | 2633.16 |
| Classic System 96X | 2606.22 |
| Essential Set 95X | 2579.28 |
Worked solution and explanation
What this really is
This is a sum-per-group wearing a leaderboard costume, and the word revenue is doing more work than it looks. Revenue is SUM(total_amount): COUNT(*) ranks products by how often they sell, and AVG(total_amount) ranks them by ticket size. On this data nearly every product sold exactly twice, so a count-based list is a pile of ties, and an average reports half the money each leader actually brought in. There is a quieter trap underneath: group by product_name and two distinct products that happen to share a name fuse into one inflated row. Miss either and you ship the wrong winners or the wrong numbers with a query that looks perfectly clean.
Working through it
Step 1: Join and carry the name
Each transaction stores only product_id, so join transactions to products on product_id to carry product_name alongside every sale. An inner join is the right call: a product with no sales cannot be a revenue leader, and sales with a missing product_id cannot be credited to anyone, so dropping both costs nothing here.
Step 2: Sum the money, do not count the rows
total_amount is the dollar value of each sale, refunds and voided sales included, so SUM(t.total_amount) per product is revenue. COUNT(*) answers 'sold most often' and AVG answers 'biggest ticket', and neither is the question being asked.
Step 3: Group on identity, then take the top five
Group by product_id (carrying product_name for display) so same-named products stay separate, sort by revenue highest first, and cap the result at 5. Grouping on the name alone would silently merge distinct products into one row.
The solution
SELECT p.product_name, SUM(t.total_amount) AS total_revenue
FROM products p
JOIN transactions t ON t.product_id = p.product_id
GROUP BY p.product_id, p.product_name
ORDER BY total_revenue DESC
LIMIT 5Common follow-up questions
- If two products share the same `product_name` but different `product_ids`, does your GROUP BY merge them? (Tests grouping granularity; GROUP BY `product_name` merges them, which may or may not be desired.)
- Should `LIMIT 5` include ties at position 5, or is truncation acceptable here? (Tests whether 'top 5' implies a strict cap or tie inclusion, and how you would express ties if asked.)
- If a product has no transactions, should it appear with zero revenue? (Tests LEFT JOIN vs INNER JOIN; a top-5 revenue list makes zero-revenue products irrelevant.)