Break Through
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
Problem
The renewal list for next quarter's ad budget looks padded to the analyst signing it off, who wants to keep only the campaigns in ad_impressions where more than one in five impressions turned into a click. Surface those campaigns with their click-through rate and the revenue they brought in, highest click-through rate first.
A click is the only vote that counts.
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 | ctr_pct | total_revenue | impressions |
|---|---|---|---|
| LOYALTY_PROGRAM | 84.52 | 215.35 | 84 |
| SUMMER_SALE_2024 | 33.33 | 16.7 | 21 |
| NEW_USER_ACQU | 26.67 | 4.4 | 15 |
| FLASH_SALE_48H | 26.67 | 5.6 | 15 |
| PRODUCT_LAUNCH_X | 25 | 5 | 16 |
Worked solution and explanation
What this really is
Underneath the budget audit this is a ratio filtered by its own value. The click-through rate, SUM(clicked) over COUNT(*), does not exist until the rows are rolled up per ad_campaign, so the more-than-one-in-five bar has to judge the rolled-up rate, never a single impression. Candidates blow it two ways. Some filter in WHERE, where each row only carries a 0 or 1 flag. Others divide two integers, which floors every campaign below 100% to 0, so the bar drops all eight campaigns and the query returns nothing.
Build it in three moves
Step 1: Roll up per ad_campaign
Every metric here is a per-campaign number, so ad_impressions is grouped by ad_campaign first. COUNT(*) is the impressions served, including the many rows where revenue is NULL.
Step 2: Compute the rate in decimal
100.0 * SUM(clicked) / COUNT(*) gives the percentage. Summing the 0/1 clicked flag counts clicks, and the 100.0 forces decimal division. SUM(revenue) skips the NULLs on unclicked impressions, but those rows stay in the denominator.
Step 3: Judge the group, then sort
The bar runs in HAVING on the unrounded ratio, so rounding can never push a campaign across the line. The output rounds to two decimals and lists the highest rate first. FLASH_SALE_48H and NEW_USER_ACQU both sit at 26.67, and either may come first.
The solution
SELECT
ad_campaign,
ROUND(100.0 * SUM(clicked) / COUNT(*), 2) AS ctr_pct,
SUM(revenue) AS total_revenue,
COUNT(*) AS impressions
FROM ad_impressions
GROUP BY ad_campaign
HAVING 100.0 * SUM(clicked) / COUNT(*) > 20
ORDER BY ctr_pct DESC- Filtering impressions
WHERE clicked = 1keeps only clicked rows. Every surviving campaign then shows a 100% rate, andimpressionsquietly counts clicks.- Filtering campaigns
HAVING 100.0 * SUM(clicked) / COUNT(*) > 20judges each campaign's full rate. Six of the eight campaigns clear it.
Common follow-up questions
- What if the bar were the average click-through rate across all campaigns? (Tests computing a global figure once and comparing each campaign's rate against it.)
- How would you stop a campaign with a handful of impressions from clearing the bar by luck? (Tests adding a minimum-volume condition alongside the rate.)
- What if you needed weekly click-through trends per campaign? (Tests bucketing `impression_time` by week and grouping on the bucket too.)