Accounted For

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

Problem

Our per-campaign attribution numbers look too good, and we suspect many ad impressions can't be tied back to a real account at all. For each campaign in ad_impressions, give the share of its impressions that belong to a known account in users, as a decimal percentage rounded to one place, most traceable first.

Every impression is a face in the crowd; some we can name, the rest are strangers. Measure the gap, campaign by campaign.

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

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

Expected output

First 5 of 8 rows
ad_campaignattributable_pct
RETARGETING_CART100
NEW_USER_ACQU100
LOYALTY_PROGRAM94
SUMMER_SALE_202476.2
PRODUCT_LAUNCH_X68.8

Worked solution and explanation

What this really is

Strip off the marketing vocabulary and this is a per-group match rate: for each campaign, what fraction of its impressions find a partner row in users? The whole problem lives in one decision, whether an impression with no matching account survives to the denominator. It has to. An impression with a null user_id, or a user_id that points at no real account, is exactly the incompleteness being measured. Reach for an inner join and those rows vanish, every campaign reports a flawless 100 percent, and you have proudly measured nothing.


Build it

Step 1: LEFT JOIN, not INNER

Join ad_impressions to users on user_id, keeping the impression table on the left. Impressions whose user_id is null, or whose user_id has no row in users, are retained with NULL user columns instead of being dropped. Those surviving rows are the point of the exercise.

Step 2: Flag the traceable ones

SUM(CASE WHEN u.user_id IS NOT NULL THEN 1 ELSE 0 END) counts only impressions that landed on a real account. Both failure modes, a null impression user_id and a user_id absent from users, leave u.user_id NULL after the join, so one condition catches both.

Step 3: Cast, divide, per campaign

Group by ad_campaign so the flag-sum and the row-count collapse to one pair per campaign. Both are integers, so cast the numerator to REAL before the arithmetic: without it, 16 * 100 / 21 floors to 76 and a 76.2 percent campaign reports 76.0. Round to one decimal and sort most traceable first; campaigns tied at 100.0 may come in either order.


The solution

Per-campaign attributable impression rate
SELECT ai.ad_campaign,
       ROUND(CAST(SUM(CASE WHEN u.user_id IS NOT NULL THEN 1 ELSE 0 END) AS REAL) * 100 / COUNT(*), 1) AS attributable_pct
FROM ad_impressions ai
LEFT JOIN users u ON ai.user_id = u.user_id
GROUP BY ai.ad_campaign
ORDER BY attributable_pct DESC
The FK is nullable and can dangle, so the join must be an outer one; that is the whole difficulty.
  • ad_impressions: impression_id int PK, user_id int FK, ad_campaign text, clicked int, revenue real
  • users: user_id int PK, username text, account_status text
  • ad_impressions references users

Common follow-up questions