Campaign Cost Effectiveness
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
- L3
- Asked in
- Uber
Problem
An analyst closing the books on 2024 and 2025 wants to see which campaigns in ad_impressions gained ground across those two full years. For each campaign, report its total revenue over the two years and the fraction of its impressions served in 2025, as a decimal between 0 and 1.
Money in, conversions out. What is the ratio?
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 | total_revenue | recent_share |
|---|---|---|
| BRAND_AWARENESS_Q1 | NULL | 1 |
| FLASH_SALE_48H | NULL | 0 |
| HOLIDAY_PROMO | NULL | 1 |
| LOYALTY_PROGRAM | 75 | 0.52 |
| NEW_USER_ACQU | NULL | 0 |
Worked solution and explanation
What this problem really is
This is a conditional share dressed up as a campaign momentum report. The total revenue is the easy half: SUM(revenue) per ad_campaign and you are done. The real question is the fraction of each campaign's two-year impressions that landed in 2025, and it hides two traps. Count the year as it comes and you count every impression as a later-year impression, so every share is 1. Divide two whole-number counts and nearly every share truncates to 0. Either way the report says nothing about which campaigns gained ground.
Building it
Step 1: Fence the two full years
strftime('%Y', impression_time) reads the year as text like '2025', and the quoted bounds are text too, so BETWEEN '2024' AND '2025' compares like with like. It is inclusive at both ends, which keeps both full years and leaves the partial 2026 out.
Step 2: Count the later year, then the whole
Inside the window a row is either 2024 or 2025. Nulling out the 2024 year leaves COUNT with only the 2025 rows, while COUNT(*) is the campaign's full two-year population. Same rows, two populations, one pass.
Step 3: Divide in decimal, one row per campaign
Both counts are integers, so CAST(... AS REAL) on the numerator keeps the division fractional. SUM(revenue) skips the empty revenue values on its own, and GROUP BY ad_campaign gives one row per campaign.
SELECT
ad_campaign,
SUM(revenue) AS total_revenue,
CAST(COUNT(NULLIF(strftime('%Y', impression_time), '2024')) AS REAL) / COUNT(*) AS recent_share
FROM ad_impressions
WHERE strftime('%Y', impression_time) BETWEEN '2024' AND '2025'
GROUP BY ad_campaign- Whole-number division
COUNT(...) / COUNT(*)divides integer by integer. A campaign with 5 of 9 impressions in 2025 reports 0, and only a campaign with every impression in the later year reports 1.- Decimal division
CAST(COUNT(...) AS REAL) / COUNT(*)keeps the fraction. The same campaign reports 0.556, and the column finally separates campaigns that leaned into 2025 from those that faded.
Common follow-up questions
- Marketing now wants the later-year share of revenue, not of impressions. What changes? (Tests moving the condition onto a summed measure instead of a count.)
- How would you flag campaigns whose share is above 0.5 in the same query? (Tests filtering on an aggregate after `GROUP BY`.)
- What breaks if the window slides to the last 24 months instead of two calendar years? (Tests replacing the text-year fence with a date range on `impression_time`.)
- How would you keep the time filter while still letting the engine skip old partitions? (Tests writing a sargable range on `impression_time` instead of a function of it.)