Spend and Rank

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
L5

Problem

Spenders tied on total spend share a leaderboard position, so the top five positions can hold more than five people. Using transactions and the usernames in users, list every spender at position 5 or better with their total spend and position, position 1 going to the biggest spender.

Five thrones at the top of the spending leaderboard.

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

Expected output

First 5 of 5 rows
usernametotalrnk
bjorn9910511.761
brian10146.962
aria_b10134.63
aiden9946.024
anika9757.445

Worked solution and explanation

What this really is

This is a tie-aware leaderboard dressed up as an exec report. Summing total_amount per user is the easy half; the half that separates candidates is where the position comes from. ORDER BY total DESC LIMIT 5 is a row cap, not a leaderboard: it has no position to return, and the day two users tie at fifth, one of them disappears on the engine's whim. ROW_NUMBER() manufactures a position but hands two equal spenders different numbers, which is a ranking nobody can defend in a review.

Step 1: Sum spend at the user grain

Inner-join users to transactions on user_id and take SUM(t.total_amount) per u.username. The inner join is deliberate: a user with no transactions has no spend and no claim to a position. Refunds and voided sales stay in, because a negative total_amount is money that came back and should come off the total.

Step 2: Position the totals, not the transactions

RANK() OVER (ORDER BY SUM(t.total_amount) DESC) is evaluated after the GROUP BY, so the window sees one row per user and orders users by their summed spend. RANK() gives equal totals the same position and skips the slots they used, the way a sports table does.

Step 3: Cut by position in the outer query

A window result cannot be filtered in the SELECT that computes it, so the totals and positions live in the ranked CTE and the outer query keeps rnk <= 5. Filtering on the position instead of counting rows is what lets a tie at fifth return six people.

Sum per user, position the totals, cut by position
WITH ranked AS (
    SELECT u.username, SUM(t.total_amount) AS total, RANK() OVER (ORDER BY SUM(t.total_amount) DESC) AS rnk
    FROM users u
    JOIN transactions t ON u.user_id = t.user_id
    GROUP BY u.username
)
SELECT username, total, rnk
FROM ranked
WHERE rnk <= 5

The window runs over the grouped rows, so it orders users by their summed spend.

A row cap
ORDER BY total DESC LIMIT 5 returns exactly five rows and no position. With two users tied at fifth, which one survives depends on how the engine happened to sort them.
A position threshold
RANK() and then WHERE rnk <= 5 returns everyone at position 5 or better, tied users together, with the position as a column of the answer.

Common follow-up questions