Product Ratings vs Sales
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
Do product categories that sell higher-rated items actually pull more revenue? An analyst wants each category's average rating across its sales in transactions, every sale counting at the rating of its product in products, next to the category's total revenue. Products that were never rated stay out of both figures.
Do higher ratings actually mean more revenue?
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 | avg_rating | total_revenue |
|---|---|---|
| Automotive | 3.4294117647058826 | 11875.26 |
| Beauty | 3.2666666666666666 | 11195.25 |
| Books | 3.0529411764705885 | 10029.87 |
| Clothing | 3.223529411764706 | 11067.06 |
| Collectibles | 4.3 | 524.88 |
Worked solution and explanation
One filter, two aggregates
This is a scoping problem dressed up as a ratings dashboard. Revenue lives only in transactions, so the join is forced, and once you join, AVG(p.rating) runs over sale rows, which is exactly the per-sale average the question asks for. The real trap is the null filter. AVG already skips a NULL rating, so WHERE p.rating IS NOT NULL looks redundant and gets deleted. Do that and avg_rating comes out identical, the spot check passes, and every category holding an unrated product that sold ships an inflated total_revenue, because SUM(t.total_amount) never looks at the rating.
- products: product_id PK, category, rating
- transactions: transaction_id PK, product_id FK, total_amount
- transactions.product_id references products.product_id
Build it in three moves
Step 1: Join transactions to products on product_id
Inner is the right join. A sale whose product_id is NULL has no category to land in, and a category with no sales has no revenue to report. After the join, each row is one sale wearing its product's rating and category.
Step 2: Drop unrated products in WHERE
Filter on p.rating IS NOT NULL before grouping. The filter is not there for the average, which skips NULLs on its own. It is there so the sales of unrated products never reach SUM(t.total_amount).
Step 3: Group by category and compute both measures
AVG(p.rating) over the joined rows weights each product by how often it sold, so a product sold three times votes three times. SUM(t.total_amount) adds the revenue as recorded, refunds and voided sales included.
SELECT
p.category,
AVG(p.rating) AS avg_rating,
SUM(t.total_amount) AS total_revenue
FROM products p
JOIN transactions t ON p.product_id = t.product_id
WHERE p.rating IS NOT NULL
GROUP BY p.category- Filter dropped
avg_ratingmatches the correct answer to the last digit, becauseAVGskips NULL ratings.total_revenuegrows in every category where an unrated product sold.- Filter kept
- Both metrics describe the same population: sales of rated products. The average and the revenue now answer one question about one set of rows.
Common follow-up questions
- What does the query return if every `rating` is NULL? (An empty result: the `WHERE` empties the input before grouping, so no category row survives.)
- How would you report the per-product average instead, one vote per product? (Tests rolling `transactions` up per `product_id` before the join, so the fan-out never reaches the average.)
- How would you keep categories with no sales, showing revenue 0? (Swap to a left join from `products` and default the sum with `COALESCE`.)
- At 10x the transactions, how do you keep the join from stalling on hot `product_id` values? (Probes broadcasting the small side versus salting a skewed key.)