One-Track Mind
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
Problem
We're profiling read-only API users: people who have only ever called the API with GET and never any other method. For each endpoint, count these read-only users, and return the endpoint or endpoints with the highest count, listing them in alphabetical order by endpoint.
Some users only ever read, never write. Find where the read-only crowd gathers.
Tables
api_calls
Columns: call_id INTEGER, endpoint TEXT, method TEXT, status INTEGER, latency REAL, user_id INTEGER, call_time TEXT, err_msg TEXT
| call_id | endpoint | method | status | latency | user_id | call_time | err_msg |
|---|---|---|---|---|---|---|---|
| 8079 | /api/v1/orders | POST | 200 | 4.2 | 100 | 2026-02-02 01:07:00 | NULL |
| 8158 | /api/v2/products | PUT | 200 | 7.9 | 197 | 2026-03-03 02:14:00 | NULL |
| 8237 | /api/v1/search | DELETE | 201 | 11.6 | 294 | 2026-04-04 03:21:00 | NULL |
| 8316 | /api/v1/auth/login | PATCH | 204 | 15.3 | 391 | 2026-05-05 04:28:00 | NULL |
| 8395 | /api/v1/auth/logout | get | 301 | 19 | NULL | 2026-06-06 05:35:00 | NULL |
| 8474 | /api/v2/analytics | post | 400 | 22.7 | 585 | 2026-07-07 06:42:00 | Not found |
| 8553 | /api/v1/payments | GET | 401 | 26.4 | 682 | 2026-08-08 07:49:00 | Unauthorized |
| 8632 | /api/v1/users/ | POST | 403 | 30.1 | 779 | 2026-09-09 08:56:00 | Rate limit exceeded |
| 8711 | /api/v2/products/ | PUT | 404 | 33.8 | 876 | 2026-01-10 09:03:00 | Internal server error |
| 8790 | /api/v1/users | DELETE | 500 | 37.5 | NULL | 2026-02-11 10:10:00 | Bad gateway |
Expected output
| endpoint | get_only_users |
|---|---|
| /api/v1/orders | 24 |
| /api/v1/users | 24 |
| /api/v2/products | 24 |
Worked solution and explanation
What this problem really is
This is a per-user set difference wearing an API-analytics costume. The real question: can you count the users who did GET and nothing but GET, broken down by endpoint? Anyone can write WHERE method = 'GET' and group by endpoint. The trap is that a plain GET filter counts every user who ever made a GET, including the ones who also POST, PUT and DELETE all day. A row filter cannot express 'has GET and has no non-GET'; you need an exclusion against the set of users who ever did something else. Skip that and your 'read-only' population balloons with users who are anything but, and every endpoint count comes out inflated.
Break down the requirements
Step 1: Identify who is NOT read-only (case-insensitive)
Build the exclusion set: every user_id that ever made a non-GET call (UPPER(method) <> 'GET', non-null user_id). Any user in this set is disqualified, no matter how many GETs they also made. Normalize with UPPER so 'get' and 'GET' collapse together.
Step 2: Keep only the read-only users' GET rows
Scan the GET rows (UPPER(method) = 'GET') and drop anyone in the exclusion set with user_id NOT IN (...). A GET row with no user_id falls out on its own: NULL NOT IN (...) is unknown, and COUNT(DISTINCT user_id) would skip it anyway, so this side needs no second guard.
Step 3: Take the top endpoints, ties and all
GROUP BY endpoint counting unique users, then keep every endpoint whose count equals (SELECT MAX(get_only_users) FROM counts) and order by endpoint. Using ORDER BY ... LIMIT 1 here silently drops tied endpoints, and the seed has a three-way tie at 24.
The solution
WITH counts AS (
SELECT endpoint,
COUNT(DISTINCT user_id) AS get_only_users
FROM api_calls
WHERE UPPER(method) = 'GET'
AND user_id NOT IN (
SELECT DISTINCT user_id
FROM api_calls
WHERE UPPER(method) <> 'GET'
AND user_id IS NOT NULL
)
GROUP BY endpoint
)
SELECT endpoint, get_only_users
FROM counts
WHERE get_only_users = (SELECT MAX(get_only_users) FROM counts)
ORDER BY endpointCommon follow-up questions
- How would you extend this to users who only ever used read methods (GET and HEAD)? (Tests generalizing the exclusion set to a method allowlist.)
- What breaks if `method` casing is inconsistent and you normalize only one side of the exclusion? (Tests casing normalization and where it must be applied.)
- How would you rewrite the exclusion as `NOT EXISTS` or a `LEFT JOIN ... IS NULL`, and why is that safer than `NOT IN`? (Tests anti-join alternatives to `NOT IN` and their NULL behavior.)