Kings of the Calendar

A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
hard
Seniority
L4
Asked in
eBay

Problem

We publish a monthly best-sellers leaderboard for an online marketplace, where a product's standing in a calendar month is set by its total units sold that month, and we only feature products that are currently in stock. For every month, surface the products holding the top three standings, along with their total units sold and standing.

Every month has a winner.

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 99 rows
monthproduct_nametotal_quantityrnk
2022-01Basic Device 61X21
2023-01Smart Gadget 97X31
2024-01Pro Unit 73X41
2025-02Ultra Tool 74X51
2026-04Premium Widget 40X13

Worked solution and explanation

What this really tests

Strip the leaderboard costume and this is a per-partition top-N with tie preservation. The month is a grouping key you derive from transaction_date; inside each month you total units per product with SUM(quantity), then assign a standing. The trap is the standing function: reach for ROW_NUMBER or a per-month LIMIT and you silently drop products tied on units, so a month with two products tied for third returns one and the interviewer never sees the other. DENSE_RANK holds every tied product at the same standing with no gaps, and because you cannot filter a window result in the same SELECT's WHERE, the standing has to live in a CTE you filter afterward. The in_stock = 1 cut also belongs in that inner query, before the aggregation, so out-of-stock products never enter the totals.


Break down the requirements

Step 1: Join and filter to stocked products

Inner-join transactions to products on product_id so you can read product_name and in_stock, then keep only rows where in_stock = 1. Doing the filter here means out-of-stock products never reach SUM(quantity).

Step 2: Total units per product per month

GROUP BY STRFTIME('%Y-%m', a.transaction_date), b.product_name with SUM(a.quantity) computes units sold at the month-and-product grain before any ranking happens.

Step 3: Assign a per-month standing with DENSE_RANK

DENSE_RANK() OVER (PARTITION BY STRFTIME('%Y-%m', a.transaction_date) ORDER BY SUM(a.quantity) DESC) restarts the standing inside each month and lets tied totals share a number without leaving a gap.

Step 4: Cut to the top three

Wrap the standings in a CTE and filter WHERE rnk <= 3 in the outer query. You have to filter outside the window's SELECT, and the <= keeps every tied product on the third rung rather than an arbitrary one.


The solution

Per-month top three with shared standings
WITH ranked AS (
    SELECT
        STRFTIME('%Y-%m', a.transaction_date) AS month,
        b.product_name,
        SUM(a.quantity) AS total_quantity,
        DENSE_RANK() OVER (PARTITION BY STRFTIME('%Y-%m', a.transaction_date) ORDER BY SUM(a.quantity) DESC) AS rnk
    FROM transactions a
    JOIN products b ON a.product_id = b.product_id
    WHERE b.in_stock = 1
    GROUP BY STRFTIME('%Y-%m', a.transaction_date), b.product_name
)
SELECT month, product_name, total_quantity, rnk
FROM ranked
WHERE rnk <= 3

Common follow-up questions