Kings of the Aisle

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

Problem

The merchandising team is reviewing which product owned each category this year. For every category, surface the single product with the highest total sales, listed from the biggest earner down.

Every category crowns exactly one winner. Go find them.

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 11 rows
categoryproduct_nametotal_revenue
ElectronicsPremium Widget 100X2713.98
ToysEssential Set 95X2579.28
GardenEco Kit 98X1330.05
AutomotiveSmart Gadget 97X1316.58
BeautyClassic System 96X1303.11

Worked solution and explanation

Why this problem exists in real interviews

This is a top-1-per-group problem wearing a merchandising costume, and it is the exact spot Visa-style SQL rounds probe your window-function instinct. Anyone can join transactions to products and sum revenue by category and product. The real question is how you collapse each category down to its single leader. Reach for MAX(total_revenue) in a second aggregation and you get the right number but lose the product_name attached to it, or you re-join and accidentally surface two products when a category has a tie. The clean move is to number the rows inside each category and keep number one.


The schema you are reasoning about

`transactions.product_id` references `products.product_id`; category lives only on products, so the join is mandatory.
  • transactions: transaction_id int PK, product_id int FK, total_amount num, transaction_date date
  • products: product_id int PK, product_name text, category text
  • transactions references products

Break down the requirements

Step 1: Join to reach the category

category and product_name live on products, the revenue lives on transactions. Inner join on product_id so every sale carries its category label. An inner join is correct here: a transaction with no matching product cannot be attributed and should drop.

Step 2: Roll up to product-in-category revenue

Filter to this year with strftime('%Y', transaction_date) = '2026', then GROUP BY category, product_name and SUM(total_amount). This is the grain the leaderboard actually cares about, one row per product per category.

Step 3: Number within each category and keep the leader

ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_revenue DESC, product_name) stamps the top earner in each category as 1. Filtering rn = 1 gives exactly one product per category, and the product_name rides along untouched, which a bare MAX() would have severed.


The solution

Top product per category
WITH product_revenue AS (
  SELECT p.category AS category,
         p.product_name AS product_name,
         SUM(t.total_amount) AS total_revenue
  FROM transactions t
  JOIN products p ON t.product_id = p.product_id
  WHERE strftime('%Y', t.transaction_date) = '2026'
  GROUP BY p.category, p.product_name
),
ranked AS (
  SELECT category,
         product_name,
         total_revenue,
         ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_revenue DESC, product_name) AS rn
  FROM product_revenue
)
SELECT category, product_name, total_revenue
FROM ranked
WHERE rn = 1
ORDER BY total_revenue DESC

Common follow-up questions