The Ones Who Clicked

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
Microsoft

Problem

A search only counts as a success when the user clicked one of its results, and on our shopping marketplace everyone in users who signed up in the same calendar year forms one cohort. List the cohorts from earliest to most recent with how many search_queries their users ran, how many of those succeeded, and the success rate.

Every signup cohort runs its searches. Find the years that turned queries into clicks.

Tables

search_queries

Columns: query_id INTEGER, user_id INTEGER, search_term TEXT, results_count INTEGER, clicked_result INTEGER, query_time TEXT

search_queries: 10 of 200 rows
query_iduser_idsearch_termresults_countclicked_resultquery_time
2071100laptop deals1722026-02-02 01:11:00
2142197phone case iphone 153432026-03-03 02:22:00
2213294usb-c cable 6ft51NULL2026-04-04 03:33:00
2284NULLgaming mouse6852026-05-05 04:44:00
2355488mechanical keyboard8562026-06-06 05:55:00
24265854k monitor102NULL2026-07-07 06:06:00
2497682standing desk11982026-08-08 07:17:00
2568NULLwebcam 1080p13692026-09-09 08:28:00
2639876bluetooth speaker153NULL2026-01-10 09:39:00
2710973smart watch17012026-02-11 10:50:00

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 3 of 3 rows
signup_cohorttotal_searchessuccessful_searchessuccess_rate
20245000
202550480.96
202650460.92

Worked solution and explanation

What this really tests

Strip the search-quality story and this is a per-cohort click-through rate over a join. Anyone can join searches to users and divide. What separates candidates is three quiet decisions. First, the cohort is the year sliced out of a text signup_date, and some rows carry a time part, so keying on the raw value gives you a bucket per signup day. Second, a NULL clicked_result is a search nobody clicked, and it stays in the denominator. Third, successful over total is integer division unless you cast. Miss the first and you return dozens of rows; miss the last and 2025's 0.96 reads 0.

Each search points at a user; the user's signup year is the cohort.
  • search_queries: query_id int PK, user_id int FK, clicked_result int, query_time timestamp
  • users: user_id int PK, signup_date date
  • search_queries.user_id references users.user_id

Building it

Step 1: Bucket each user by signup year

A cohort is the calendar year of signup_date, stored as text, sometimes with a time part ('2025-08-08 16:34:00'). strftime('%Y', u.signup_date) reads the year off either form and returns a four-character label. Do it once in the cohort CTE so the join downstream stays clean.

Step 2: Attach searches to cohorts with an inner join

Join search_queries to cohort on user_id. The inner join is deliberate: searches whose user_id is NULL, the orphan rows in the sample, fall out, because a search with no owner belongs to no cohort. An outer join would carry them into a phantom NULL bucket.

Step 3: Aggregate in one pass, then cast before dividing

COUNT(*) gives total searches per cohort. SUM(CASE WHEN sq.clicked_result IS NOT NULL THEN 1 ELSE 0 END) counts the successful ones in the same scan. Cast that sum to REAL before dividing by the total, or the engine does integer division and 48 over 50 comes back as 0.


The solution

Per-cohort search success
WITH cohort AS (
    SELECT u.user_id, strftime('%Y', u.signup_date) AS signup_cohort
    FROM users u
)
SELECT
    c.signup_cohort,
    COUNT(*) AS total_searches,
    SUM(CASE WHEN sq.clicked_result IS NOT NULL THEN 1 ELSE 0 END) AS successful_searches,
    CAST(SUM(CASE WHEN sq.clicked_result IS NOT NULL THEN 1 ELSE 0 END) AS REAL) / COUNT(*) AS success_rate
FROM search_queries sq
JOIN cohort c ON sq.user_id = c.user_id
GROUP BY c.signup_cohort
ORDER BY c.signup_cohort
Naive
WHERE clicked_result IS NOT NULL, then COUNT(*) as successful, a second query for totals, and successful / total as-is. The filter drops failed searches from the denominator and integer division flattens the rate.
Correct
One pass: COUNT(*) for total, SUM(CASE WHEN clicked_result IS NOT NULL ...) for successful, cast to REAL before dividing. Denominator intact, rate fractional.

Common follow-up questions