Return on a Glance

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

Domain
SQL
Difficulty
easy
Seniority
L3
Asked in
General Assembly

Problem

The marketing analytics team is benchmarking every ad campaign on how much each impression earns. For each campaign, give the number of impressions it served and its revenue per impression, where an impression that brought in nothing still counts, from the best earners down.

Every impression costs something. Find the campaigns earning it back.

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

Expected output

First 5 of 8 rows
ad_campaignimpressionsavg_revenue_per_impression
LOYALTY_PROGRAM842.5637
SUMMER_SALE_2024210.7952
HOLIDAY_PROMO160.3875
FLASH_SALE_48H150.3733
BRAND_AWARENESS_Q1170.2118

Worked solution and explanation

What this really tests

This is revenue per impression served, disguised as a campaign leaderboard. The skill being probed: do you know that AVG(revenue) quietly drops every NULL-revenue impression before it divides, so it answers 'average revenue among impressions that earned something', not revenue per impression served? Across the table only about one impression in seven carries any revenue, so AVG(revenue) divides by that earning minority and reports a figure several times too high. Fold every empty impression to a 0 with AVG(COALESCE(revenue, 0)) and the average divides by all served impressions, which is exactly why you also surface COUNT(*): it is the denominator the figure rests on. Reach for AVG(revenue) and every campaign inflates by a different amount, so the whole ranking reshuffles.


Why AVG(revenue) betrays you

AVG(revenue)
Averages only the impressions where revenue is not NULL, because AVG skips nulls before it divides. With most impressions earning nothing, it divides by the earning minority alone and reports a figure far above revenue per impression. It answers 'average revenue among impressions that earned something', a question nobody asked.
AVG(COALESCE(revenue, 0))
Folds every empty impression to a 0 before averaging, so the divisor is every impression served. This is revenue per impression, the figure the team compares across campaigns. It equals SUM(revenue) over COUNT(*) written as one aggregate, and COUNT(*) is the impression total worth reporting alongside it.

Take LOYALTY_PROGRAM: across all 84 impressions it served, AVG(COALESCE(revenue, 0)) divides its total revenue by all 84 and returns 2.5637. Swap in AVG(revenue) and the divisor collapses to just the impressions that earned something, so the same campaign reports a number several times larger. Every campaign inflates this way, and because they inflate by different amounts, the ranking itself moves, not just the values.


Building it

Step 1: Count the impressions per campaign

GROUP BY ad_campaign sets the grain to one row per campaign, and COUNT(*) on each group is the number of impressions that campaign served. That same count is the denominator the per-impression average has to divide by, so surfacing it makes the benchmark auditable at a glance.

Step 2: Fold empty impressions to zero

AVG divides by however many rows it sees, but it never sees a NULL-revenue row. Wrap each revenue in COALESCE(revenue, 0) so an empty impression enters the average as a real 0 and stays in the denominator. That single change turns 'average of the earners' into 'revenue per impression served'.

Step 3: Order highest first

ORDER BY avg_revenue_per_impression DESC puts the best earners on top. The campaign averages come out distinct, so the sort is already a total order and needs no tiebreaker.


The solution

Impressions and revenue per impression, per campaign
SELECT ad_campaign,
       COUNT(*) AS impressions,
       ROUND(AVG(COALESCE(revenue, 0)), 4) AS avg_revenue_per_impression
FROM ad_impressions
GROUP BY ad_campaign
ORDER BY avg_revenue_per_impression DESC;

Common follow-up questions