You are asked to design a SQL solution for 'the top 3 products by revenue in each category'. How do you start?
Start by pinning down the grain, because almost every mistake in this kind of question comes from joining at the wrong level. Ask two questions out loud: what does one row of the output represent, and what does one row of each input table represent. A product can appear on many order lines, so a join between products and order lines multiplies product rows. Revenue is a sum over the order lines, so it has to be aggregated before it can be compared or ranked.
Then state the plan out loud: build the revenue per product, rank the products inside each category, keep the top three, and join the result back to the product names for display. Ranking before joining back to the dimension table is the shape that keeps the query correct.
CREATE TABLE categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE products (
id INTEGER PRIMARY KEY,
category_id INTEGER NOT NULL,
name TEXT NOT NULL,
price INTEGER NOT NULL
);
CREATE TABLE order_lines (
id INTEGER PRIMARY KEY,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL
);
INSERT INTO categories (id, name) VALUES
(1, 'Peripherals'),
(2, 'Displays');
INSERT INTO products (id, category_id, name, price) VALUES
(1, 1, 'keyboard', 80),
(2, 1, 'mouse', 25),
(3, 1, 'monitor stand', 40),
(4, 1, 'webcam', 60),
(5, 2, 'monitor', 300);
INSERT INTO order_lines (id, product_id, quantity) VALUES
(1, 1, 2),
(2, 1, 1),
(3, 4, 3),
(4, 5, 1),
(5, 5, 2);
WITH product_revenue AS (
SELECT p.id,
p.category_id,
p.name,
SUM(p.price * ol.quantity) AS revenue
FROM products p
JOIN order_lines ol ON ol.product_id = p.id
GROUP BY p.id, p.category_id, p.name
),
ranked AS (
SELECT pr.*,
DENSE_RANK() OVER (PARTITION BY category_id ORDER BY revenue DESC) AS rnk
FROM product_revenue pr
)
SELECT c.name AS category, r.name AS product, r.revenue
FROM ranked r
JOIN categories c ON c.id = r.category_id
WHERE r.rnk <= 3
ORDER BY c.name, r.revenue DESC, r.name;Peripherals returns the keyboard at 240, the webcam at 180, and the mouse at 25. The monitor stand never sold, so it is absent — an inner join on order lines drops it, which is usually what you want for a revenue ranking but worth confirming out loud. Displays returns the monitor at 900.
Two details an interviewer will look for. First, revenue is price * quantity aggregated over the lines, not a stored column, because price changes over time and a stored total silently disagrees with the current price. Second, DENSE_RANK rather than ROW_NUMBER: if two products tie on revenue, DENSE_RANK gives them the same rank, so a tie at rank 2 does not push the third product out of the top three. If the business rule says exactly three products and ties should be broken arbitrarily, ROW_NUMBER with a deterministic tiebreaker is the alternative — say which one you chose and why.