The Weight of a Click

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
L5
Asked in
Mercury Insurance

Problem

event_data holds one row per user action, so anyone who repeats an action appears once per repeat. For page_view, search, checkout_start and purchase, report how many unique users performed each event and that count's share of the four counts added together, as a decimal percentage rounded to one place.

Where users actually spend their attention.

Tables

event_data

Columns: event_id INTEGER, event_type TEXT, user_id INTEGER, event_timestamp TEXT, payload TEXT, tags TEXT, properties TEXT

event_data: 10 of 200 rows
event_idevent_typeuser_idevent_timestamppayloadtagsproperties
3059button_click1002026-02-02 01:07:00{"button_id": "cancel", "element_class": "btn-primary", "position": {"x": 13, "y": 7}}["desktop", "web", "returning"]{"os": "Edge", "language": "US", "screen_size": "en", "utm_source": "mobile"}
3118purchase1972026-03-03 02:14:00{"amount": 56.93, "currency": "GBP", "items": [{"sku": "SKU1002", "qty": 3}]}["ios", "premium", "new_user", "organic"]{"country": "Windows", "device_type": "FR", "referrer": "fr", "utm_medium": "1920x1080", "browser": "twitter"}
3177signup2942026-04-04 03:21:00{"signup_method": "apple", "referral_code": "REF3X", "accepted_terms": true}["android", "free", "campaign_a", "referral", "experiment_v1"]{"language": "iOS", "screen_size": "es", "utm_source": "desktop", "session_id": "bing", "os": "organic"}
3236login3912026-05-05 04:28:00{"login_method": "password", "remember_me": true, "mfa_used": false}["web", "returning"]{"device_type": "JP", "referrer": "ja", "utm_medium": "1440x900"}
3295logout4882026-06-06 05:35:00{"action": "logout", "value": 50}["premium", "new_user", "organic"]{"screen_size": "de", "utm_source": "tablet", "session_id": "amazon", "os": "Chrome"}
3354search5852026-07-07 06:42:00{"query": "phone", "results_count": 102, "filters": {"category": "electronics"}}["free", "campaign_a", "referral", "experiment_v1"]{"referrer": "mobile", "utm_medium": "375x812", "browser": "email", "country": "Edge", "device_type": "US"}
3413add_to_cartNULL2026-08-08 07:49:00{"product_id": 1007, "quantity": 2, "price": 58.49, "variant": "medium"}["returning", "campaign_b", "featured", "experiment_v2", "ios"]{"utm_source": "fr", "session_id": "twitter", "os": "Safari", "language": "Windows", "screen_size": "FR"}
3472open7792026-09-09 08:56:00{"action": "open", "value": 80}["new_user", "organic"]{"utm_medium": "bing", "browser": "organic", "country": "iOS"}
3531crash8762026-01-10 09:03:00{"action": "crash", "value": 90}["campaign_a", "referral", "experiment_v1"]{"session_id": "direct", "os": "Firefox", "language": "macOS", "screen_size": "JP"}
3590error9732026-02-11 10:10:00{"action": "error", "value": 100}["campaign_b", "featured", "experiment_v2", "ios"]{"browser": "Chrome", "country": "Android", "device_type": "DE", "referrer": "de", "utm_medium": "tablet"}

Expected output

First 4 of 4 rows
event_typeunique_userspct_of_engaged
checkout_start721.9
page_view515.6
purchase1340.6
search721.9

Worked solution and explanation

What this really is

This is a per-event distinct-user count dressed up as an engagement report. The skill being probed is whether you count people, not events, and whether you know what the denominator actually is. Anyone can write a GROUP BY. Candidates fall in two places: they reach for COUNT(*), so a user who searched ten times counts as ten, and they assume the denominator is the number of distinct engaged users when it is really the sum of the four per-event counts. Get the second one wrong and the shares stop summing to 100 and diverge from the expected output. There is a quieter third trap too: every number in that division is an integer.


Walking through it

Step 1: Scope to the four events

Restrict event_data to 'page_view', 'search', 'checkout_start' and 'purchase' up front with IN. Everything else (logins, crashes, opens) is noise that would otherwise inflate the denominator, and filtering in WHERE shrinks the set the engine has to dedupe.

Step 2: Count people, not events

COUNT(DISTINCT user_id) per event_type collapses a user's repeated events to a single head. This is the line that turns an event log into a people metric. Rows with a NULL user_id are not people, and COUNT(DISTINCT user_id) already skips them.

Step 3: Divide by the shared total

The denominator is SUM(unique_users) over the four grouped rows, evaluated once as a scalar subquery and not redefined as distinct users across all four events. A user who both searched and purchased lands in two buckets, so this sum is engagement weight, which is exactly why the shares total 100.

Step 4: Convert before dividing, then round

unique_users is an integer, 100 is an integer, and the summed denominator is an integer, so the division truncates. CAST(unique_users AS REAL) makes the whole expression real, and ROUND(..., 1) then delivers the one-decimal figure the statement asks for.

The solution

Grouped distinct-user counts with a shared scalar denominator
WITH counts AS (
  SELECT event_type, COUNT(DISTINCT user_id) AS unique_users
  FROM event_data
  WHERE event_type IN ('page_view','search','checkout_start','purchase')
  GROUP BY event_type
)
SELECT event_type, unique_users,
       ROUND(CAST(unique_users AS REAL) * 100 / (SELECT SUM(unique_users) FROM counts), 1) AS pct_of_engaged
FROM counts
Distinct users across the four events
Defining the denominator as COUNT(DISTINCT user_id) over all four events counts each engaged person once. The four shares then do NOT sum to 100, because a user in two buckets is counted twice in the numerators but once below. This does not match the expected output.
Sum of the four per-event counts
SUM(unique_users) adds the four bucket counts, so every numerator is part of the denominator. The shares sum to 100 (up to rounding) and read as share of engagement weight, which is the intended metric.

Common follow-up questions