Above Category Average

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

Problem

Every row in transactions is one sale of a product listed in products, and each product belongs to exactly one category. Find the products whose average sale amount beats the average over every sale in their category, with both averages shown side by side.

The category average is one thing. These beat it.

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

transactions

Columns: transaction_id INTEGER, product_id INTEGER, total_amount REAL, user_id INTEGER, quantity INTEGER, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 46 rows
product_nameproduct_avgcategory_avg
Ultra Tool 14X198.57131.22
Turbo Pack 49X670.02660.23
Basic Device 51X696.96657.967894736842
Deluxe Bundle 52X710.43657.2589473684211
Pro Unit 53X723.9679

Worked solution and explanation

What the problem really is

This is a two-grain comparison dressed up as a merchandising report: every product is judged against a bar computed at a coarser grain over the same sales. Anyone can average one product's sales. What separates candidates is where the category bar comes from. It has to pool every sale in the category, not average the product averages already in hand. Reuse the product-level numbers and a product with one sale counts as much as a product with three, the bar moves, and products land on the wrong side of it.


Break down the requirements

Step 1: Compute each product's mean transaction amount

First CTE: join products to transactions on product_id, GROUP BY product_id, product_name, category, and compute AVG(total_amount). Products with no sales never appear, and sales whose product_id is NULL fall out of the join.

Step 2: Compute each category's pooled mean

Second CTE: join the same two tables, GROUP BY category, and compute AVG(total_amount). That is SUM(total_amount) / COUNT(*) across every sale in the category, so a product weighs in by how many times it sold. It is not an average of per-product averages.

Step 3: Join the two CTEs on category

Each product maps to exactly one category, so each product row picks up exactly one category average. The result stays at product grain with the pooled bar attached.

Step 4: Keep strictly-above products

WHERE product_avg > cat_avg. Strictly greater, because matching the category is not beating it. Return product_name, product_avg, and cat_avg renamed to category_avg.


The solution

Two-grain aggregation with strict filter
WITH product_avg AS (
    SELECT p.product_id,
           p.product_name,
           p.category,
           AVG(t.total_amount) AS product_avg
    FROM products p
    JOIN transactions t ON t.product_id = p.product_id
    GROUP BY p.product_id, p.product_name, p.category
),
category_avg AS (
    SELECT p.category,
           AVG(t.total_amount) AS cat_avg
    FROM products p
    JOIN transactions t ON t.product_id = p.product_id
    GROUP BY p.category
)
SELECT pa.product_name,
       pa.product_avg,
       ca.cat_avg AS category_avg
FROM product_avg pa
JOIN category_avg ca ON ca.category = pa.category
WHERE pa.product_avg > ca.cat_avg

Common follow-up questions