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

Each row in transactions is one purchase, however many units it moved, made by a user whose account status sits in users, of a product whose category sits in products. For every account status, find the product category with the most purchases, keeping every category that ties for the lead, and list the statuses alphabetically.

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 segment talk away and this is a per-group argmax: inside each account_status, which category has the most transactions rows. Everyone can chain transactions to users and products and count. What separates candidates is counting the right thing and scoping 'the most' to each status. A purchase is a row, not a unit: sum quantity instead of COUNT(*) and three of the four statuses change leaders, with 'suspended' handing its spot from 'Books' to a 'Music' and 'Sports' tie. Pick the top category over the whole table instead of per status and every segment gets the same answer.


Build it in three moves

Step 1: Count purchases per status and category

Inner-join transactions to users on user_id for account_status, and to products on product_id for category, then COUNT(*) per pair in purchase_counts. One row is one purchase, so a five-unit order still counts once. The 18 transactions with no product_id have no category and drop out of the join. That is correct: they cannot lead any category, and none of the four leaders changes because of them.

Step 2: Rank categories inside each status

RANK() OVER (PARTITION BY account_status ORDER BY purchase_count DESC) restarts the ordering for every status and reads the grouped pairs, not the raw transactions. RANK gives a 1 to every category tied for the lead, which is exactly the promise the statement makes about ties.

Step 3: Keep the leaders and sort by status

Filter to status_rank = 1 and order by account_status alone. Alphabetical status is the only order asked for; co-leaders inside one status, if they ever appear, may come in any order, so no tiebreaker belongs in the sort.


The solution

Top category per account status
WITH purchase_counts AS (
  SELECT
    u.account_status,
    p.category,
    COUNT(*) AS purchase_count
  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
),
ranked AS (
  SELECT
    account_status,
    category,
    purchase_count,
    RANK() OVER (
      PARTITION BY account_status
      ORDER BY purchase_count DESC
    ) AS status_rank
  FROM purchase_counts
)
SELECT account_status, category, purchase_count
FROM ranked
WHERE status_rank = 1
ORDER BY account_status;
Counting rows
COUNT(*) per pair crowns 'Automotive' for 'active', 'Garden' for 'inactive', 'Clothing' for the pending-verification segment and 'Books' for 'suspended': four rows, one leader each.
Summing units
SUM(quantity) per pair splits 'active' three ways at 24 units, gives the pending-verification segment to 'Music' at 30 and splits 'suspended' between 'Music' and 'Sports' at 30: seven rows, three statuses wrong.

Common follow-up questions

Common follow-up questions