High-Spend 2025 Campaigns
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
Problem
A campaign only counts as earning in a calendar year if its impressions brought in more than five dollars across that year. From ad_impressions, we need every campaign and year that cleared that bar, with how many different people the campaign reached in that year.
Big-budget campaigns from last year.
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 | impression_year | unique_users |
|---|---|---|
| FLASH_SALE_48H | 2026 | 5 |
| HOLIDAY_PROMO | 2026 | 5 |
| LOYALTY_PROGRAM | 2021 | 12 |
| LOYALTY_PROGRAM | 2022 | 12 |
| SUMMER_SALE_2024 | 2026 | 11 |
Worked solution and explanation
What this really asks
Strip off the earnings-review costume and this is a grouped sum gated by a threshold, keyed on two things at once: the campaign and the calendar year hiding inside a text timestamp. Anyone can write SUM(revenue). What separates candidates is where each piece lands. The year has to become a grouping key, the five-dollar bar has to run after the totals exist, and reach has to dedupe repeat visitors. Group by campaign alone and you get one blended total per campaign that hides which years earned; count rows and you report impressions wearing a reach label.
Step 1: Pull the year out of the timestamp
impression_time is a full timestamp stored as text. EXTRACT(YEAR FROM impression_time) turns it into the calendar year. Campaign names like 'SUMMER_SALE_2024' contain a year too, and that is bait: the year that counts is when the impression was served.
Step 2: Group on campaign and year together
GROUP BY ad_campaign, EXTRACT(YEAR FROM impression_time) gives one bucket per campaign per year. Drop the year and every year blends into one total; group by the raw impression_time instead and each bucket shrinks to a single impression.
Step 3: Two aggregates, two jobs
SUM(revenue) lives in HAVING, because the bar is on the group's total. COUNT(DISTINCT user_id) is the reach you select: the same person is served a campaign several times in a year, and the dedupe folds those repeats to one. Both aggregates skip NULLs, so impressions with no revenue add nothing and a NULL user_id adds no person.
SELECT ad_campaign,
EXTRACT(YEAR FROM impression_time) AS impression_year,
COUNT(DISTINCT user_id) AS unique_users
FROM ad_impressions
GROUP BY ad_campaign, EXTRACT(YEAR FROM impression_time)
HAVING SUM(revenue) > 5Group on campaign and extracted year, gate each group with HAVING SUM(revenue) > 5, and report people with COUNT(DISTINCT user_id).
SELECT ad_campaign,
EXTRACT(YEAR FROM impression_time) AS impression_year,
COUNT(*) AS unique_users
FROM ad_impressions
GROUP BY ad_campaign, EXTRACT(YEAR FROM impression_time)
HAVING SUM(revenue) > 5COUNT(*) counts impressions and NULL user_id rows, so reach is overstated wherever a user came back.
- Counting impressions
COUNT(*)tallies every row in the group, so a user shown a campaign five times that year reports a reach of 5, and NULLuser_idrows count too.- Counting people
COUNT(DISTINCT user_id)folds each user's impressions in the group to one and drops NULLuser_id, so the number means how many different people saw the campaign that year.
Common follow-up questions
- Now also return each campaign-year's total revenue, highest first. (Tests selecting the aggregate itself and sorting on it.)
- How would you list every campaign-year with a column saying whether it cleared the bar? (Tests keeping a group the bar removes, with a flag instead of a filter.)
- What changes if user identity lives in a separate `users` table and impressions only store a device id? (Pushes toward a join before the distinct count and deduping identity across devices.)