Impressions by Search Keyword

A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
hard
Seniority
L3
Asked in
Meta

Problem

Someone who ran several searches in search_queries is still one member of the audience, so each impression they received in ad_impressions counts once, not once per search. Leaving out the time-boxed sale campaigns (any ad_campaign with 'SALE' in its name), ad sales wants every other campaign's impressions that reached a searcher, from most impressions to least.

Campaign performance, keyword by keyword.

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

search_queries

Columns: query_id INTEGER, user_id INTEGER, search_term TEXT, results_count INTEGER, clicked_result INTEGER, query_time TEXT

search_queries: 10 of 200 rows
query_iduser_idsearch_termresults_countclicked_resultquery_time
2071100laptop deals1722026-02-02 01:11:00
2142197phone case iphone 153432026-03-03 02:22:00
2213294usb-c cable 6ft51NULL2026-04-04 03:33:00
2284NULLgaming mouse6852026-05-05 04:44:00
2355488mechanical keyboard8562026-06-06 05:55:00
24265854k monitor102NULL2026-07-07 06:06:00
2497682standing desk11982026-08-08 07:17:00
2568NULLwebcam 1080p13692026-09-09 08:28:00
2639876bluetooth speaker153NULL2026-01-10 09:39:00
2710973smart watch17012026-02-11 10:50:00

Expected output

First 5 of 6 rows
ad_campaignimpression_count
LOYALTY_PROGRAM58
BRAND_AWARENESS_Q117
RETARGETING_CART16
NEW_USER_ACQU15
PRODUCT_LAUNCH_X7

Worked solution and explanation

What this really is

This is a fan-out join dressed up as a campaign-reach report. Anyone can join search_queries to ad_impressions on user_id and count by campaign. What separates candidates is noticing that search_queries holds one row per search, not one per person. Each search row re-matches every impression that user ever saw, so a user with two searches counts every ad twice. Miss it and every campaign's reach is inflated, quietly, with numbers that still look plausible.


Build it in order

Step 1: Collapse searches to searchers

SELECT DISTINCT user_id FROM search_queries turns searches into searchers. The statement says a person is one audience member, so this is the grain the join needs. Doing it first keeps the join input small and the count honest.

Step 2: Join the searchers to their impressions

Inner-join searchers to ad_impressions on user_id. Impressions with a null user_id match nobody and drop out; a campaign that reached no searcher never appears.

Step 3: Drop the sale campaigns

ad_campaign NOT LIKE '%SALE%' removes 'FLASH_SALE_48H' and 'SUMMER_SALE_2024'. The word sits in the middle of one name and the end of the other, so both wildcards matter.

Step 4: Count per campaign, biggest first

GROUP BY ad_campaign, count the rows as impression_count, and sort by it descending. That is the whole order the statement asks for; campaigns tied on the count may come back either way.

One row per searcher, then join and count
SELECT ai.ad_campaign, COUNT(*) AS impression_count
FROM (
  SELECT DISTINCT user_id
  FROM search_queries
) searchers
INNER JOIN ad_impressions ai ON ai.user_id = searchers.user_id
WHERE ai.ad_campaign NOT LIKE '%SALE%'
GROUP BY ai.ad_campaign
ORDER BY impression_count DESC;

The derived table searchers holds one row per person who searched.

The one-step join that overcounts
SELECT ai.ad_campaign, COUNT(*) AS impression_count
FROM search_queries sq
INNER JOIN ad_impressions ai ON ai.user_id = sq.user_id
WHERE ai.ad_campaign NOT LIKE '%SALE%'
GROUP BY ai.ad_campaign
ORDER BY impression_count DESC;

Every search row re-joins all of that user's impressions.

Dedupe after the join
COUNT(DISTINCT ai.impression_id) over the raw join lands on the same numbers, but the engine first materializes every fanned-out row and only then throws the duplicates away.
Dedupe before the join
DISTINCT user_id removes the repeats on the small side, so the join never produces them and a plain COUNT(*) is correct.

Common follow-up questions