First Among Many

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
Meta

Problem

We group users by account status, and every segment leans toward one product category more than the rest. For each status, surface the category with the most purchases, where a purchase is a single transaction regardless of how many units it moved. Walk through the segments from the lowest account status upward, and when a status has two categories tied for the lead, let the one that comes first alphabetically take the spot.

Each segment has a favorite category.

Tables

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

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 4 of 4 rows
account_statuscategorypurchase_count
activeAutomotive8
inactiveGarden8
pending_verificationClothing9
suspendedBooks9

Worked solution and explanation

What this is really asking

Strip the marketing veneer and this is top-per-group with ties kept, where the group is account_status and the winner count is one. Everyone can join three tables and count; the ranking step is what separates candidates. Aggregate to one row per (status, category) first, then rank those groups inside each status. Reach for ROW_NUMBER and you silently pick one winner when two categories tie, dropping legitimate answers; RANK keeps every category that reaches the top count. Over 200M transactions you also have no choice but to aggregate before you rank, so you rank a few hundred groups, not rows.


Break down the requirements

Step 1: Join and count per pair

Join transactions to users (for account_status) and products (for category), then COUNT(*) grouped by (account_status, category). That collapses 200M rows to at most a few hundred groups. Note it is COUNT(*) of rows, not SUM(quantity): a five-unit basket is still one purchase, and weighting by quantity flips winners in any segment with a big-basket outlier.

Step 2: Rank inside each segment

RANK() OVER (PARTITION BY account_status ORDER BY COUNT(*) DESC). Using RANK keeps every tied top category; ROW_NUMBER would collapse a real tie to one arbitrary winner and quietly delete the rest.

Step 3: Filter and order

Keep rk = 1 and ORDER BY account_status, category. The outer sort is by category name, not by count, because all surviving rows already share the top count within their segment.


The solution

TOP CATEGORY PER ACCOUNT STATUS
WITH ranked AS (
  SELECT
    u.account_status,
    p.category,
    COUNT(*) AS purchase_count,
    RANK() OVER (
      PARTITION BY u.account_status
      ORDER BY COUNT(*) DESC
    ) AS rk
  FROM transactions t
  JOIN users u    ON t.user_id    = u.user_id
  JOIN products p ON t.product_id = p.product_id
  GROUP BY u.account_status, p.category
)
SELECT account_status, category, purchase_count
FROM ranked
WHERE rk = 1
ORDER BY account_status, category;

COMMON FOLLOW-UP QUESTIONS

Common follow-up questions