The Campaign Trail
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
- Asked in
- Uber
Problem
The ad_impressions table logs every ad shown along with the revenue it earned, so a campaign's money is spread across many rows. Return the campaigns whose revenue booked through the end of June 2026 adds up to more than $4.50, listed alphabetically by name.
Impressions are vanity. Conversions are sanity.
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 |
|---|
| FLASH_SALE_48H |
| LOYALTY_PROGRAM |
| PRODUCT_LAUNCH_X |
| SUMMER_SALE_2024 |
Worked solution and explanation
This is a filter-the-total problem wearing a renewal-review costume. Each campaign's money is scattered across a few clicked impressions worth a couple of dollars apiece, and no single row on this data earns more than $3.75, so the $4.50 bar is a property of the campaign's sum, never of an impression. Anyone can write the grouping. What separates candidates is judging the bar after the totals exist while keeping the June cutoff in front of them. Put the bar in WHERE revenue > 4.5 and the list comes back empty. Drop the cutoff and 'HOLIDAY_PROMO' sneaks in on July money it had not earned yet.
Three moves, in the order the engine runs them
Step 1: Cut the timeline at June 2026
impression_time is ISO text, so strftime('%Y-%m', impression_time) yields a 'YYYY-MM' key that compares correctly as a string. Keep rows <= '2026-06'. This belongs before the grouping because it decides which rows a total is made of.
Step 2: Total each campaign
Group by ad_campaign and take SUM(revenue). Unclicked impressions carry NULL revenue and SUM skips them, which is exactly the business meaning: they earned nothing.
Step 3: Judge the total, then sort
HAVING SUM(revenue) > 4.5 runs once per campaign, after the sum exists. It is strictly greater, so a campaign at exactly $4.50 stays out. Finish with ORDER BY ad_campaign; the names are one per group, so the order is fully decided.
SELECT ad_campaign
FROM ad_impressions
WHERE strftime('%Y-%m', impression_time) <= '2026-06'
GROUP BY ad_campaign
HAVING SUM(revenue) > 4.5
ORDER BY ad_campaign- Bar on each impression
WHERE revenue > 4.5asks whether one impression earned $4.50. None did; the largest single row is $3.75, so the answer is an empty list.- Bar on each campaign
HAVING SUM(revenue) > 4.5asks whether the campaign earned $4.50 in total.'PRODUCT_LAUNCH_X'gets there with $5.00 from four rows, none of them above $2.75.
Common follow-up questions
- How would you return each qualifying campaign's total next to its name? (Aliasing `SUM(revenue)` once in the select list and reading it in the bar instead of computing it twice.)
- Renewal now goes to the top three campaigns by revenue through June. What changes? (Moves from a fixed bar to ordering by the total with a limit, and asks how a tie at the cut is settled.)
- What if `revenue` were stored as integer cents? (Tests whether the bar becomes `450` and whether any averaging would hit integer division.)