Top Products per Category

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
L4
Asked in
Visa

Problem

Each row of the transactions table is one sale with the revenue it brought in, and the products table puts every product in a category. List the top five products by total sales revenue in each category, categories alphabetically and the biggest sellers first within each.

Five winners per category.

Tables

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, 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

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 52 rows
categoryproduct_nametotal_sales
AutomotiveSmart Gadget 97X2633.16
AutomotiveSmart Gadget 87X2363.76
AutomotiveSmart Gadget 67X1824.96
AutomotiveSmart Gadget 57X1555.56
AutomotiveSmart Gadget 47X1286.16

Worked solution and explanation

This is a per-group cutoff dressed up as a merchandising report. Anyone can total revenue per product; what separates candidates is whether 'top five' is measured inside each category or across the whole catalog. Write ORDER BY total_sales DESC LIMIT 5 and you get five rows for the entire store: on this seed that is one product apiece from 'Electronics', 'Garden', 'Automotive', 'Beauty' and 'Toys', and the other six categories, the two-product 'Collectibles' shelf included, silently disappear.

Total first, then cut inside each category

Step 1: Collapse sales to one row per product

Inner-join transactions to products on product_id and take SUM(total_amount) per category and product_name. total_amount is already the revenue of the sale, so never multiply by quantity again; the voided sale at 0 and the refund at -45 simply pull their product's total down. Sales whose product_id is NULL match nothing and drop out, which is what a per-product report wants.

Step 2: Number the products inside each category

Number each product within its category, highest total_sales first, with product_name behind it so two equal totals could never fight over the fifth slot. The count restarts at 1 for every category, and that restart is the whole problem.

Step 3: Cut at five one level up, then sort

Keep sales_rank <= 5 in an outer query: the number does not exist until the window has run, so it cannot be filtered in the SELECT that computes it. Sort by category, then total_sales descending. 'Collectibles' holds only two products and keeps both: a cutoff never pads.

Total per product, number within category, keep five
WITH product_sales AS (
    SELECT p.category, p.product_name, SUM(t.total_amount) AS total_sales
    FROM transactions t
    JOIN products p ON t.product_id = p.product_id
    GROUP BY p.category, p.product_name
),
ranked AS (
    SELECT category, product_name, total_sales,
           ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sales DESC, product_name) AS sales_rank
    FROM product_sales
)
SELECT category, product_name, total_sales
FROM ranked
WHERE sales_rank <= 5
ORDER BY category, total_sales DESC
One cutoff for the store
ORDER BY total_sales DESC LIMIT 5 keeps the five biggest products overall: one each from 'Electronics', 'Garden', 'Automotive', 'Beauty' and 'Toys', and nothing at all from the other six categories.
One cutoff per category
ROW_NUMBER() with PARTITION BY category restarts the count on every shelf, so each category contributes up to five rows: 52 in all, because 'Collectibles' only has two to give.

Common follow-up questions