Two Names, One Campaign

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
L4

Problem

Our advertising team labels campaigns in loud uppercase (ad_impressions.ad_campaign, e.g. FLASH_SALE_48H) while the push-notification team writes lowercase slugs (push_notifs.campaign, e.g. flash_sale), and the two naming systems were never reconciled. Some are plainly the same effort under a different name because they share a theme word: 'flash', 'loyalty', or 'summer'. Pair every ad campaign with the push campaign it secretly matches on one of those theme words, walking through the ad campaign names in alphabetical order.

The ad team and the push team never agreed on naming. Find where they secretly meant the same thing.

Tables

ad_impressions

Columns: impression_id INTEGER, user_id INTEGER, ad_campaign TEXT, impression_time TEXT, clicked INTEGER, revenue REAL

ad_impressions: 10 of 200 rows
impression_iduser_idad_campaignimpression_timeclickedrevenue
253100BRAND_AWARENESS_Q12026-02-02 01:07:000NULL
306197PRODUCT_LAUNCH_X2026-03-03 02:14:000NULL
359294RETARGETING_CART2026-04-04 03:21:000NULL
412391HOLIDAY_PROMO2026-05-05 04:28:000NULL
465488NEW_USER_ACQU2026-06-06 05:35:0010.2
518NULLLOYALTY_PROGRAM2026-07-07 06:42:000NULL
571682FLASH_SALE_48H2026-08-08 07:49:000NULL
624779SUMMER_SALE_20242026-09-09 08:56:000NULL
677876BRAND_AWARENESS_Q12026-01-10 09:03:000NULL
730973PRODUCT_LAUNCH_X2026-02-11 10:10:0010.35

push_notifs

Columns: notif_id INTEGER, user_id INTEGER, title TEXT, platform TEXT, status TEXT, sent_at TEXT, opened INTEGER, campaign TEXT

push_notifs: 10 of 200 rows
notif_iduser_idtitleplatformstatussent_atopenedcampaign
1053100New message from teamandroidpending2026-02-02 01:07:000winback_feb
1106197Weekly digest readywebfailed2026-03-03 02:14:000promo_summer
1159294Price drop alertiOSbounced2026-04-04 03:21:001onboard_v2
1212391Security alertAndroidDelivered2026-05-05 04:28:000NULL
1265488Payment receivedWEBFAILED2026-06-06 05:35:00NULLloyalty_2024
1318585Complete your profilebasicdelivered2026-07-07 06:42:001NULL
1371682New feature availablepremiumpending2026-08-08 07:49:000reengagement
1424779Subscription renewaliosfailed2026-09-09 08:56:000NULL
1477876Flash sale: 50% offandroidbounced2026-01-10 09:03:001flash_sale
1530973Your order shipped!webDelivered2026-02-11 10:10:00NULLretention_q1

Expected output

First 3 of 3 rows
ad_campaignpush_campaign
FLASH_SALE_48Hflash_sale
LOYALTY_PROGRAMloyalty_2024
SUMMER_SALE_2024promo_summer

Worked solution and explanation

What you're actually being asked

Strip the marketing costume and this is a fuzzy join across a keyword whitelist. Two systems named the same campaign differently, and the only bridge between them is a shared theme word. Nobody hands you 'FLASH_SALE_48H' = 'flash_sale' as a key; you manufacture the join predicate yourself out of LOWER plus LIKE. Anyone can type JOIN. What separates candidates is where they put the LOWER and how they group the keyword conditions.

The trap: precedence and one-sided casing

Step 1: Normalize both sides

Wrap both campaign columns in LOWER so casing stops mattering. Case is the whole reason the two teams' data never lined up, so fix it at the comparison, not by rewriting the source.

Step 2: Build one predicate per theme word

For each keyword, require it to appear on the ad side AND the push side. That AND is what makes it a genuine match rather than 'either side happens to mention flash'.

Step 3: Dedupe the pairs

ad_impressions carries many rows per campaign, so the raw join emits the same (ad, push) pair once per impression. DISTINCT collapses them back to one row per real match.

Matched campaign pairs
SELECT DISTINCT
       ai.ad_campaign,
       pn.campaign AS push_campaign
FROM   ad_impressions ai
JOIN   push_notifs pn
  ON  (LOWER(ai.ad_campaign) LIKE '%flash%'   AND LOWER(pn.campaign) LIKE '%flash%')
   OR (LOWER(ai.ad_campaign) LIKE '%loyalty%' AND LOWER(pn.campaign) LIKE '%loyalty%')
   OR (LOWER(ai.ad_campaign) LIKE '%summer%'  AND LOWER(pn.campaign) LIKE '%summer%')
ORDER BY ai.ad_campaign;

One AND-pair per theme word, OR'd together, both sides lowered, deduped.

Naive: equality join
JOIN push_notifs pn ON ai.ad_campaign = pn.campaign returns zero rows. The uppercase names and the lowercase slugs are never equal strings.
Correct: keyword bridge
Match on a shared lowered keyword with LOWER(...) LIKE '%kw%' on both sides, OR-ing one AND-pair per theme word, then DISTINCT.

Common follow-up questions