Heavy Ad Exposure
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
- L5
- Asked in
- Shell
Problem
A user counts as heavily ad-exposed if they have 3 or more impressions from a single campaign, or 5 or more total impressions spread across at least 2 different campaigns. Return the IDs of the users who qualify.
Saturated with ads. Is it too much?
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
| user_id |
|---|
| 100 |
| 197 |
| 294 |
| 391 |
| 488 |
Worked solution and explanation
What this problem really is
Strip off the ad-exposure costume and this is an OR of two thresholds that live at two different grain levels. Rule one is about a single campaign's count for a user; rule two is about a user's grand total and their campaign variety. The skill being probed: can you compute both from one rollup instead of scanning the table twice and stitching the halves with a UNION? The trap is the grain. People evaluate the 3-impression rule against a user's total, which silently qualifies anyone busy across many campaigns and breaks the whole definition.
The move that cracks it
Roll the table up to one row per (user_id, ad_campaign) with its impression count. Now everything you need is a second-level aggregate over that result, per user: MAX of those campaign counts is the hottest single campaign (rule one), SUM is the user's total impressions and COUNT is how many distinct campaigns they touched (rule two). One pass, no UNION, no dedup.
Step 1: Collapse to per-campaign counts first
Build per_campaign: GROUP BY user_id, ad_campaign with COUNT(*). Filter out NULL user_id here, before any counting, so anonymous impressions never pollute a real user's totals.
Step 2: Read both rules off the rollup
Aggregate per_campaign by user_id into per_user: MAX(impressions) is the hottest campaign, SUM(impressions) is the total, COUNT(*) is the distinct-campaign count. Then filter with a plain WHERE: hottest_campaign >= 3 (rule one) OR (total_impressions >= 5 AND campaign_count >= 2) (rule two). OR them so either path qualifies.
WITH per_campaign AS (
SELECT user_id, ad_campaign, COUNT(*) AS impressions
FROM ad_impressions
WHERE user_id IS NOT NULL
GROUP BY user_id, ad_campaign
),
per_user AS (
SELECT user_id,
MAX(impressions) AS hottest_campaign,
SUM(impressions) AS total_impressions,
COUNT(*) AS campaign_count
FROM per_campaign
GROUP BY user_id
)
SELECT user_id
FROM per_user
WHERE hottest_campaign >= 3
OR (total_impressions >= 5 AND campaign_count >= 2)- UNION of two scans
- Two full GROUP BY passes over a 400M-row table, then a UNION to merge and dedup. More SQL, two scans, and the per-campaign threshold is easy to misplace.
- One rollup, OR the rules
- Collapse to per-(user, campaign) counts once, aggregate to one row per user, then read both rules off MAX, SUM, and COUNT in a single WHERE over that much smaller set.
Common follow-up questions
- Now restrict the 5-impression rule to impressions within any rolling 7-day window. (Tests whether they can add a windowed condition on top of the rollup.)
- Return which rule each user qualified under, and both counts. (Tests extending the output beyond a flag to a reason.)
- Of these heavily exposed users, which ones never generated any revenue? (Tests joining the qualifier set back to revenue.)