Point of Entry
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
- L4
- Asked in
- Meta
Problem
The growth team is checking whether the people who arrive and simply look around ever turn into buyers. For every user whose very first recorded event was a page view, count how many purchases they later made, and keep the ones who never bought with a count of zero. Order the result from the most purchases down, breaking ties by user ID ascending.
Everyone starts by looking. Count who came back to buy.
Tables
event_data
Columns: event_id INTEGER, event_type TEXT, user_id INTEGER, event_timestamp TEXT, payload TEXT, tags TEXT, properties TEXT
| event_id | event_type | user_id | event_timestamp | payload | tags | properties |
|---|---|---|---|---|---|---|
| 3059 | button_click | 100 | 2026-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"} |
| 3118 | purchase | 197 | 2026-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"} |
| 3177 | signup | 294 | 2026-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"} |
| 3236 | login | 391 | 2026-05-05 04:28:00 | {"login_method": "password", "remember_me": true, "mfa_used": false} | ["web", "returning"] | {"device_type": "JP", "referrer": "ja", "utm_medium": "1440x900"} |
| 3295 | logout | 488 | 2026-06-06 05:35:00 | {"action": "logout", "value": 50} | ["premium", "new_user", "organic"] | {"screen_size": "de", "utm_source": "tablet", "session_id": "amazon", "os": "Chrome"} |
| 3354 | search | 585 | 2026-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"} |
| 3413 | add_to_cart | NULL | 2026-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"} |
| 3472 | open | 779 | 2026-09-09 08:56:00 | {"action": "open", "value": 80} | ["new_user", "organic"] | {"utm_medium": "bing", "browser": "organic", "country": "iOS"} |
| 3531 | crash | 876 | 2026-01-10 09:03:00 | {"action": "crash", "value": 90} | ["campaign_a", "referral", "experiment_v1"] | {"session_id": "direct", "os": "Firefox", "language": "macOS", "screen_size": "JP"} |
| 3590 | error | 973 | 2026-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
| user_id | purchase_count |
|---|---|
| 12128 | 5 |
| 12031 | 4 |
| 11934 | 3 |
| 11837 | 2 |
| 11740 | 1 |
Worked solution and explanation
What this really is
Strip the activity-tracking costume and this is a cohort definition stacked on a conditional count. You carve out a population by each user's FIRST event, then count a DIFFERENT event type for exactly those users. The trap lives in four words of the prompt: 'with a count of zero'. Define the cohort, switch to an inner join to count purchases, and every viewer who never bought silently disappears from your result. The grader is counting those exact people, so an inner join quietly deletes the rows you were asked to keep.
Break down the requirements
Step 1: Define the cohort by first event type
Use a CTE with ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_timestamp) and filter to rn = 1 AND event_type = 'page_view'. This is the earliest event of ANY type, then a check that it was a page view, not 'the first page_view in their history'.
Step 2: Count purchase events for the cohort
Left join the cohort users back to event_data filtered to event_type = 'purchase' and count the matched event rows per user.
Step 3: Keep the zero-purchase viewers
Because it is a LEFT JOIN, a viewer with no purchase rows still produces one output row, and COUNT over the null join partner returns 0 instead of dropping them. Swap in an inner join here and those users vanish.
The solution
WITH first_events AS (
SELECT user_id, event_type,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_timestamp) AS rn
FROM event_data
),
viewer_cohort AS (
SELECT user_id
FROM first_events
WHERE rn = 1 AND event_type = 'page_view'
)
SELECT
vc.user_id,
COUNT(e.event_id) AS purchase_count
FROM viewer_cohort vc
LEFT JOIN event_data e
ON vc.user_id = e.user_id AND e.event_type = 'purchase'
GROUP BY vc.user_id
ORDER BY purchase_count DESC, vc.user_id ASCCommon follow-up questions
- What if two events share the same timestamp for a user? (`ROW_NUMBER` is non-deterministic on ties; add `event_id` as a secondary sort key so the first event is reproducible.)
- How would you compute the conversion rate (purchasers over total cohort)? (Count cohort users with `purchase_count` > 0 divided by total cohort size.)
- What if you needed the elapsed time between first view and first purchase? (Join each user's first `page_view` timestamp to their first purchase timestamp and take the difference.)
- How would you generalize this to a cohort defined by any first-event type? (Parameterize the `event_type` literal in the cohort CTE so any first-event type can define the cohort.)