Clean Averages

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

Across the products table, which categories have at least three rated products, and for each one, how many rated products does it have and what is their average rating to one decimal place? A product that was never rated has no score and counts toward neither figure.

Merchandising only cares about the categories customers actually rate.

Tables

products

Columns: product_id INTEGER, product_name TEXT, category TEXT, price REAL, rating REAL, in_stock INTEGER

products: 10 of 200 rows
product_idproduct_namecategorypriceratingin_stock
1001Basic Device 1XBooks17.522.71
1050Deluxe Bundle 2XClothing25.054.41
1099Pro Unit 3XHome & Kitchen32.582.11
1148Ultra Tool 4XCollectibles40.113.81
1197Essential Set 5XToys47.641.51
1246Classic System 6XBeauty03.21
1295Smart Gadget 7XAutomotive62.74.90
1344Eco Kit 8XGarden70.232.61
1393Turbo Pack 9XMusic77.764.30
1442Premium Widget 10XElectronics85.2921

Expected output

First 5 of 10 rows
categoryrated_productsavg_rating
Automotive193.4
Beauty192.9
Books183
Garden182.8
Sports173.3

Worked solution and explanation

What this is really asking

This is a population-alignment problem dressed up as a quality report. Two numbers come back per category, and they have to describe the same set of products. Anyone can write AVG(rating). The trick is that AVG quietly skips null ratings while COUNT(*) counts every row, so a query with no filter returns a correct average sitting next to a wrong count. Get it wrong and every category with an unscored product overstates how many ratings stand behind its average, and a thin category can sneak past the three-product bar.

Step 1: Drop unrated products first

WHERE rating IS NOT NULL runs before grouping, so every aggregate after it sees only products that were actually scored. One filter, and both figures inherit it.

Step 2: Count and average per category

GROUP BY category with COUNT(*) as rated_products and ROUND(AVG(rating), 1) as avg_rating. Because the unrated rows are already gone, COUNT(*) now means rated products, not products.

Step 3: Apply the three-product bar

HAVING COUNT(*) >= 3 cuts categories after aggregation. It is checked against the same rated population the count reports, so the bar and the number on screen can never disagree.

Rated-only count and average
SELECT category, COUNT(*) AS rated_products, ROUND(AVG(rating), 1) AS avg_rating
FROM products
WHERE rating IS NOT NULL
GROUP BY category
HAVING COUNT(*) >= 3
No filter
COUNT(*) includes products with a NULL rating, so rated_products is inflated wherever a category holds an unscored product. AVG(rating) still looks right, which is exactly why nobody catches it.
Filter first
WHERE rating IS NOT NULL shrinks the population once. COUNT(*), AVG(rating) and the HAVING bar all measure the same rated products.

Common follow-up questions