Average Rating by Category
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
- Asked in
- Yelp
Problem
Show the average customer rating of each category in the products catalog, best-loved first, for the PM's roadmap review.
Category ratings. Some shine, some don't.
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 |
Expected output
| category | avg_rating |
|---|---|
| Collectibles | 4.3 |
| Automotive | 3.431578947368421 |
| Sports | 3.323529411764706 |
| Books | 2.9611111111111112 |
| Beauty | 2.936842105263158 |
Worked solution and explanation
This is a mean over a column with holes in it, dressed up as a roadmap readout. Anyone can group products by category. What separates candidates is whether the denominator counts only the products that were actually rated. Thirteen of the 200 products have a NULL rating, spread over ten of the eleven categories. Divide by every product, or zero-fill the gaps, and ten categories read low: 'Books' slides from fourth to a tie for sixth, and the PM walks into the meeting flagging the wrong category.
Getting there
Step 1: Pick the grain: one row per category
The PM compares categories, so the output has one row per category value and nothing else. This seed has eleven, including 'Collectibles', which holds only two products.
Step 2: Let AVG(rating) own the denominator
AVG skips NULL inputs, so each category's mean is divided by its rated products only. That is the business meaning too: a product nobody has rated yet carries no verdict about its category. Writing SUM(rating) / COUNT(*) puts those unrated rows back in the denominator.
Step 3: Keep every product, in stock or not
A rating records what customers thought of a product, not whether it is on the shelf today. Every 'Music' product is out of stock, so an in_stock = 1 filter would erase that category from the readout entirely.
Step 4: Sort best-loved first
Order by avg_rating descending. The eleven averages all differ on this data, so that one key fixes the row order.
SELECT
category,
AVG(rating) AS avg_rating
FROM products
GROUP BY category
ORDER BY avg_rating DESC- Divides by every product
SUM(rating) / COUNT(*)orAVG(COALESCE(rating, 0))counts the 13 unrated products. 'Books' lands at 2.665 instead of 2.961 and ties 'Electronics' for sixth.- Divides by rated products
AVG(rating)ignores theNULLratings. 'Books' is 53.3 over its 18 rated products, 2.961, fourth on the list.
Common follow-up questions
- The PM wants an average shown only when at least five products in the category are rated. Where does that rule go, and what do you count? (Tests filtering groups on an aggregate, and counting `rating` rather than rows.)
- How would you report the share of each category's products that are still unrated? (Tests `COUNT(rating)` versus `COUNT(*)` as a deliberate measure.)
- 'Collectibles' tops the list on just two products. How would you keep a tiny category from looking like the best-loved? (Tests a minimum-volume threshold or a shrunk average.)