Category Revenue
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
Problem
The finance team is assembling a category performance slide for the quarterly business review. For each product category they want the total revenue, the number of transactions behind it, and the average transaction size, leaving off any category whose total revenue comes in under $12,100. List the categories from highest revenue to lowest.
Which categories pull their weight?
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
| category | total_revenue | transaction_count | avg_transaction_size |
|---|---|---|---|
| Electronics | 15016.8 | 20 | 750.8399999999999 |
| Books | 12501.39 | 19 | 657.967894736842 |
| Clothing | 12487.92 | 19 | 657.2589473684211 |
| Home & Kitchen | 12222 | 18 | 679 |
Worked solution and explanation
What this problem is really about
Underneath the slide costume this is a threshold on a grouped total. Anyone can total revenue per category; the tell is where the $12,100 cutoff lands. It compares each category's SUM(total_amount), a number that does not exist until the rows are grouped, so it cannot live in WHERE. Reference the aggregate there and the query errors. Test total_amount there instead and you are filtering single sales, none of which comes anywhere near $12,100, so the slide comes back empty. Forget the cutoff entirely and every category, the two-product Collectibles line included, lands in front of the executives.
Break down the requirements
Step 1: Join products to transactions
Join on product_id to attach category to each transaction. The category lives only on products, so every sale has to be fanned out to its category before anything can be totaled. Sales whose product_id is empty have no category to land in, and the inner join drops them.
Step 2: Aggregate three metrics
SUM(t.total_amount) for revenue, COUNT(*) for transaction count, and AVG(t.total_amount) for average transaction size, all grouped by p.category in a single pass.
Step 3: Filter on the total, then sort
HAVING SUM(t.total_amount) >= 12100 drops the categories that fall under the slide cutoff. This has to be HAVING, not WHERE, because the value being tested is the grouped total. Then ORDER BY total_revenue DESC puts the survivors highest first.
The solution
SELECT
p.category,
SUM(t.total_amount) AS total_revenue,
COUNT(*) AS transaction_count,
AVG(t.total_amount) AS avg_transaction_size
FROM products p
JOIN transactions t ON p.product_id = t.product_id
GROUP BY p.category
HAVING SUM(t.total_amount) >= 12100
ORDER BY total_revenue DESCCommon follow-up questions
- What if the cutoff was the median category revenue instead of a fixed dollar figure? (Tests computing the median of the category totals in a CTE and referencing it in `HAVING`.)
- How would you handle NULLs in `total_amount`? (`SUM` and `AVG` skip NULLs but `COUNT(*)` does not, so the average and the count would describe different rows.)
- What if you needed to add a percentage-of-total column? (Tests a grand total via `SUM(total_amount) OVER ()` computed over the grouped rows.)