SQL ยท medium

Find the top 3 products by revenue within each category.

Asked in Data Analyst interviews, in the SQL round.

Short answer

RANK()/ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) <= 3.

Sample data

These tables are loaded into an in-browser engine on this page, so you can run your own answer against them. No account needed.

products

product_idnamecategoryrevenue
1Keyboardelectronics890
2Headphoneselectronics1290
3Mouseelectronics430
4Webcamelectronics430
5Hubelectronics210
6Desk Lamphome480
7Water Bottlehome190
8Chairhome950
9Rughome320
10Backpacktravel740
11Cable Bagtravel150

How to answer it

Definition

A top-N-per-group problem. One global ORDER BY ... LIMIT 3 gives the top three overall; the ranking has to restart inside every category, which is what PARTITION BY does.

Example

WITH ranked AS (
  SELECT category,
         product_id,
         revenue,
         DENSE_RANK() OVER (PARTITION BY category ORDER BY revenue DESC) AS rnk
  FROM   products
  WHERE  revenue IS NOT NULL
)
SELECT category, product_id, revenue
FROM   ranked
WHERE  rnk <= 3
ORDER BY category, revenue DESC, product_id;

Output

category    | product_id | revenue
------------+------------+--------
electronics |          2 |    1290
electronics |          1 |     890
electronics |          3 |     430
electronics |          4 |     430
home        |          8 |     950
home        |          6 |     480
home        |          9 |     320
travel      |         10 |     740
travel      |         11 |     150

Run the query against the products sample table above. Electronics returns four products because Mouse and Webcam tie at the third revenue level. Travel has only two products, so both qualify. NULL revenue is excluded; decide whether unknown revenue should instead be treated as zero.

Which ranking function

revenue   ROW_NUMBER   RANK   DENSE_RANK
  91200            1      1            1
  77400            2      2            2
  77400            3      2            2
  64100            4      4            3

For the four-row comparison above, filtering each function to <= 3 gives: ROW_NUMBER: three rows; RANK: three rows with ranks 1, 2, 2 (the next rank is 4); DENSE_RANK: all four rows, covering three distinct revenue levels. Categories with fewer than three products return fewer than three rows.

Exactly three products per category

Use ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC, product_id ASC) in place of DENSE_RANK when the business wants at most three products, with product_id as a stable tie-breaker. In the sample this chooses Mouse ahead of Webcam. Do not add product_id to DENSE_RANK's window ordering: that would separate products with equal revenue and defeat tie preservation.

When the input contains individual orders

The sample already has one revenue total per product. For transaction data, first SUM revenue grouped by category and product_id, then rank those totals. Define the reporting period and treatment of refunds before aggregating. Ranking individual order rows answers a different question.

Interview follow-ups

Key points

FAQ

The 30-second answer

A window function partitioned by category and ordered by revenue descending, filtered to rank three or below in an outer query, since a window function cannot go in WHERE.
The choice worth narrating is which ranking function. ROW_NUMBER gives at most three rows; a unique tie-breaker makes selection stable. RANK keeps ties and skips subsequent numbers. DENSE_RANK gives the top three revenue levels, which may be more than three products. I would default to DENSE_RANK and ask what should happen when two products tie on the boundary, because that is a product decision rather than a SQL one.

In the interview

Practice this for real