Top Identified Event Types

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

Find the three event types with the most events in event_data, keeping only the types where events carrying a user_id outnumber anonymous ones. The growth analytics team reads that list busiest first, and when types tie for a spot, the one earliest in the alphabet takes it.

The top users by events, but only the identifiable ones.

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 3 of 3 rows
event_typetotal_events
purchase29
button_click14
crash13

Worked solution and explanation

What this really is

This is a per-group qualification test dressed up as a popularity chart. Counting events per type is the easy half. What separates candidates is comparing two sub-counts of the same group (rows with a user_id against rows without one) and filtering on that comparison after grouping, then cutting three rows where the cut lands in a tie. Skip the comparison and page_view, with 17 events of which 12 are anonymous, takes second place. Skip the tie rule and the third row is any of seven types sitting at 13.

Building it

Step 1: Count every event per event_type

Group event_data by event_type and take COUNT(*). Every row counts toward the total, anonymous ones included: the ask is the busiest types, not the busiest logged-in types, so nothing gets filtered out before the count.

Step 2: Qualify the type, not the rows

Identified versus anonymous is a property of the whole type, so the test runs after grouping. Two conditional sums inside HAVING, one counting rows where user_id IS NOT NULL and one where user_id IS NULL, compare the two halves of the same group. The comparison is strict, so an even split does not qualify.

Step 3: Sort, break the tie, then cut

Sort by total_events descending. Here purchase (29) and button_click (14) are clear, then seven types tie at 13. event_type ASC is the statement's alphabetical rule and decides that crash takes the third slot; only after that does LIMIT 3 cut.

Conditional counts qualify each type; the tie key picks the third
SELECT event_type, COUNT(*) AS total_events
FROM event_data
GROUP BY event_type
HAVING SUM(CASE WHEN user_id IS NOT NULL THEN 1 ELSE 0 END) > SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END)
ORDER BY total_events DESC, event_type ASC
LIMIT 3
Filter rows first
WHERE user_id IS NOT NULL throws anonymous rows away before counting. The anonymous half of the comparison no longer exists, and total_events becomes an identified-only count. It matches here only because purchase, button_click and crash have no anonymous events; next week's data will not be so kind.
Compare inside the group
Keep every row, count both halves with conditional sums, and let HAVING drop the type. COUNT(*) stays the true total, and page_view is excluded because 12 of its 17 events are anonymous.

Common follow-up questions