Top Earner Per Campaign
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
- General Assembly
Problem
Our campaign recap names exactly one top earner per campaign, even when several users brought in the same top revenue. Rebuild it from ad_impressions with every top earner and their total revenue, leaving out impressions with no user attached, campaigns in alphabetical order and tied earners by user id.
The top-earning user per campaign.
Tables
ad_impressions
Columns: impression_id INTEGER, user_id INTEGER, ad_campaign TEXT, impression_time TEXT, clicked INTEGER, revenue REAL
| impression_id | user_id | ad_campaign | impression_time | clicked | revenue |
|---|---|---|---|---|---|
| 253 | 100 | BRAND_AWARENESS_Q1 | 2026-02-02 01:07:00 | 0 | NULL |
| 306 | 197 | PRODUCT_LAUNCH_X | 2026-03-03 02:14:00 | 0 | NULL |
| 359 | 294 | RETARGETING_CART | 2026-04-04 03:21:00 | 0 | NULL |
| 412 | 391 | HOLIDAY_PROMO | 2026-05-05 04:28:00 | 0 | NULL |
| 465 | 488 | NEW_USER_ACQU | 2026-06-06 05:35:00 | 1 | 0.2 |
| 518 | NULL | LOYALTY_PROGRAM | 2026-07-07 06:42:00 | 0 | NULL |
| 571 | 682 | FLASH_SALE_48H | 2026-08-08 07:49:00 | 0 | NULL |
| 624 | 779 | SUMMER_SALE_2024 | 2026-09-09 08:56:00 | 0 | NULL |
| 677 | 876 | BRAND_AWARENESS_Q1 | 2026-01-10 09:03:00 | 0 | NULL |
| 730 | 973 | PRODUCT_LAUNCH_X | 2026-02-11 10:10:00 | 1 | 0.35 |
Expected output
| ad_campaign | user_id | total_revenue |
|---|---|---|
| BRAND_AWARENESS_Q1 | 488 | 3.6 |
| FLASH_SALE_48H | 488 | 5.6 |
| HOLIDAY_PROMO | 973 | 4.35 |
| LOYALTY_PROGRAM | 2913 | 22.5 |
| LOYALTY_PROGRAM | 3204 | 22.5 |
Worked solution and explanation
This is a top-with-ties problem dressed up as a campaign leaderboard. Everyone writes the GROUP BY ad_campaign, user_id and the SUM(revenue). What separates candidates is what they do when two users land on the same total. On this data the loyalty campaign has two users at exactly 22.5, so the right answer is 9 rows for 8 campaigns. Reach for a one-winner pick and you return 8, quietly crowning one of them. Nobody notices, because 8 rows for 8 campaigns looks exactly right.
Three moves, in this order
Step 1: Total each user inside each campaign
Drop rows where user_id is NULL first. Those impressions belong to nobody, and if you leave them in they form a phantom user who competes for the top. Then GROUP BY ad_campaign, user_id with SUM(revenue). The grain is the whole game: rank impressions instead of totals and the winner is whoever had the single biggest click, not the biggest earner.
Step 2: Rank the totals per campaign, keeping ties
RANK() OVER (PARTITION BY ad_campaign ORDER BY SUM(revenue) DESC) is evaluated after the GROUP BY, so it can sort by the aggregate directly. RANK gives equal totals the same number, so both 22.5 users get 1. Without the PARTITION BY you get one company-wide winner instead of one per campaign.
Step 3: Keep rank 1, then sort the output
A window result cannot be filtered in the WHERE of the query that computes it, so filter rnk = 1 one level out. Then sort by ad_campaign and user_id, which puts tied users side by side in a predictable order.
SELECT ad_campaign, user_id, total_revenue
FROM (
SELECT
ad_campaign,
user_id,
SUM(revenue) AS total_revenue,
RANK() OVER (PARTITION BY ad_campaign ORDER BY SUM(revenue) DESC) AS rnk
FROM ad_impressions
WHERE user_id IS NOT NULL
GROUP BY ad_campaign, user_id
) ranked
WHERE rnk = 1
ORDER BY ad_campaign, user_id- One winner per campaign
ROW_NUMBER()filtered to 1, or a per-campaignORDER BY ... LIMIT 1, always yields exactly one row per campaign. Here that is 8 rows, and which tied user survives depends on the order the engine happened to read.- Every tied winner
RANK()filtered to 1 keeps every user whose total equals the campaign maximum: 9 rows.DENSE_RANK()behaves identically at rank 1; the two only differ further down.
SELECT ad_campaign, user_id, total_revenue
FROM (
SELECT
ad_campaign,
user_id,
SUM(revenue) AS total_revenue,
ROW_NUMBER() OVER (PARTITION BY ad_campaign ORDER BY SUM(revenue) DESC) AS rn
FROM ad_impressions
WHERE user_id IS NOT NULL
GROUP BY ad_campaign, user_id
) ranked
WHERE rn = 1
ORDER BY ad_campaign, user_idSame shape, 8 rows: ROW_NUMBER breaks the tie arbitrarily and drops a top earner.
Common follow-up questions
- How would you return the top three earners per campaign? (Swaps `rnk = 1` for `rnk <= 3` and probes how `RANK` and `DENSE_RANK` differ at the cut.)
- Should unclicked impressions count toward a user's revenue at all? (They carry `NULL` revenue, so `SUM` already ignores them; the point is noticing it.)
- How would you add each top earner's click-through rate in that campaign? (Adds `AVG(clicked)` to the same grouped query and carries it through the rank filter.)