Diminishing Returns

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

Some searches in search_queries never logged how many results came back, so they can't be sized and get their own unknown group. Every other search is small below 100 results, medium from 100 to 299 and large from 300 up, and we want each group's click-through rate as a percentage rounded to two decimals, highest rate first.

When search comes back thin, does the click still follow?

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

Expected output

First 4 of 4 rows
result_bucketclick_through_pct
small69.57
medium65.79
large65.63
unknown0

Worked solution and explanation

What this is really testing

Strip the search-analytics costume and this is a bucket-then-rate with a hole in the bucketing column. Everyone writes the size bands. What separates candidates is where the searches with no results_count land. A NULL fails results_count < 100 and results_count < 300 alike, so a CASE ending in ELSE 'large' quietly files those 14 unsized searches as large. None of them has a click, so the large rate drops from 65.63 to 53.85, and the trend you report is partly a logging gap. The second trap is the click itself: clicked_result holds the clicked position, or NULL when nothing was clicked, so the signal is presence, not value.

Build it in two moves

Step 1: Label each search by size, NULL first

Turn results_count into a label with a CASE: IS NULL becomes 'unknown', below 100 'small', below 300 'medium', and everything else 'large'. The NULL test has to lead. Every comparison against NULL is unknown, so an unsized search slides past each band and lands wherever the ELSE points. Ascending bands with strict < let each branch rely on the earlier ones having failed, so 299 is medium and 300 is large without restating lower bounds.

Step 2: Rate the clicks inside each label

Inside each label, a click is any row where clicked_result IS NOT NULL. Count those with SUM(CASE WHEN clicked_result IS NOT NULL THEN 1 ELSE 0 END) and divide by COUNT(*) for the label. The 100.0 scales to a percentage and also forces decimal division; without it, the integer ratio truncates every rate to 0. ROUND(..., 2) gives the two decimals.

The solution

Label with a `NULL` guard, then a conditional rate per label
SELECT
    CASE
        WHEN results_count IS NULL THEN 'unknown'
        WHEN results_count < 100 THEN 'small'
        WHEN results_count < 300 THEN 'medium'
        ELSE 'large'
    END AS result_bucket,
    ROUND(100.0 * SUM(CASE WHEN clicked_result IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS click_through_pct
FROM search_queries
GROUP BY result_bucket
ORDER BY click_through_pct DESC
Wrong: equals 1
clicked_result = 1 counts only clicks on the first result. Every click on position 2 or lower reads as a non-click, so every rate is understated.
Right: is not null
clicked_result IS NOT NULL counts every search that ended in a click, whatever the position, and treats only the NULL rows as non-clicks.

Common follow-up questions