Campaign Click Rate

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
Asked in
Meta

Problem

Each row of ad_impressions is one ad shown to a user, with clicked flagging whether it was clicked, and only impressions served to users who also appear in page_views belong in this report. Give each campaign's clicked impressions as a percentage of those impressions, highest rate first, with campaigns on equal rates listed alphabetically.

Among engaged users, which campaigns landed.

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

page_views

Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT

page_views: 10 of 200 rows
view_idpage_urluser_idreferrerdur_msdeviceviewed_at
5053/products1555amazon.com637desktop2026-09-02 01:09:00
5106/checkout1652twitter.com774tablet2026-09-03 02:18:00
5159/profile1749github.com911Mobile2026-09-04 03:27:00
5212/search1846NULL1048DESKTOP2026-09-05 04:36:00
5265/about1943reddit.com1185mobile2026-09-06 05:45:00
5318/blog/intro1555NULLNULLdesktop2026-09-07 06:54:00
5371/pricing1652bing.com1459tablet2026-09-08 07:03:00
5424/docs/api1749news.ycombinator.com1596Mobile2026-09-09 08:12:00
5477/settings1846NULL1733DESKTOP2026-09-10 09:21:00
5530/Home1943bing.com1870mobile2026-09-11 10:30:00

Expected output

First 5 of 8 rows
ad_campaignclicked_pct
HOLIDAY_PROMO50
PRODUCT_LAUNCH_X42.86
SUMMER_SALE_202433.33
FLASH_SALE_48H26.67
NEW_USER_ACQU26.67

Worked solution and explanation

What this really is

This is a per-campaign click rate wearing a membership filter as a costume. The real skill: restrict ad_impressions to users who also show up in page_views WITHOUT letting that second table multiply your rows. Anyone can write the conditional average. The trap is reaching for an inner JOIN page_views to express 'also appears there': page_views holds roughly 50 rows per user, so each impression fans out about 50 times, and because the skew is per-user, your numerator and denominator inflate by different amounts and the whole ranking scrambles. Keep the impression grain intact with user_id IN (SELECT user_id FROM page_views) and the counts stay honest.


Building it

Step 1: Restrict to engaged users with a semi-join

Filter ad_impressions down to rows whose user_id appears in page_views, using WHERE ai.user_id IN (SELECT user_id FROM page_views). This is a semi-join: it tests membership and returns each impression at most once, so nothing fans out. Impressions with a NULL user_id never match and fall away on their own.

Step 2: Count clicks over impressions per campaign

GROUP BY ai.ad_campaign, then split each campaign's impressions into clicked and total. The numerator is SUM(CASE WHEN ai.clicked=1 THEN 1 ELSE 0 END) (clicked is a 0/1 flag, so you sum the flag), and the denominator is COUNT(*), every qualifying impression including repeat impressions from the same user.

Step 3: Force real division, scale, and order

Both SUM(...) and COUNT(*) are integers, and integer / integer truncates toward zero, so a raw SUM(...)/COUNT(*) returns 0 for every campaign under 100%. CAST(...AS REAL) on the numerator forces real division, then *100 scales to a percentage and ROUND(...,2) trims to two places. Finish with ORDER BY clicked_pct DESC, ai.ad_campaign so ties break alphabetically.


The solution

Semi-join with a conditional click-rate
SELECT ai.ad_campaign, ROUND(CAST(SUM(CASE WHEN ai.clicked=1 THEN 1 ELSE 0 END) AS REAL)/COUNT(*)*100,2) AS clicked_pct FROM ad_impressions ai WHERE ai.user_id IN (SELECT user_id FROM page_views) GROUP BY ai.ad_campaign ORDER BY clicked_pct DESC, ai.ad_campaign

Common follow-up questions