The Vanishing Rows
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
- L3
- Asked in
- Clear Capital
Problem
Return each content view's ID, user ID and username by matching content_views to users on user_id. Our dashboard built on that result shows fewer views than content_views actually holds, since some views were logged with no user attached, so be ready to explain exactly which rows fall out and why.
Some records disappear when the tables meet. Figure out why.
Tables
content_views
Columns: view_id INTEGER, content_id INTEGER, user_id INTEGER, device_id INTEGER, viewed_at TEXT, watch_seconds INTEGER
| view_id | content_id | user_id | device_id | viewed_at | watch_seconds |
|---|---|---|---|---|---|
| 7053 | 372 | 1361 | 404 | 2026-02-02 01:07:00 | 41 |
| 7106 | 501 | 2622 | 607 | 2026-03-03 02:14:00 | 72 |
| 7159 | 630 | 3883 | 810 | 2026-04-04 03:21:00 | 103 |
| 7212 | 759 | 5144 | 1013 | 2026-05-05 04:28:00 | 134 |
| 7265 | 888 | 6405 | 1216 | 2026-06-06 05:35:00 | 165 |
| 7318 | 1017 | 7666 | 1419 | 2026-07-07 06:42:00 | 196 |
| 7371 | 1146 | NULL | 1622 | 2026-08-08 07:49:00 | 227 |
| 7424 | 1275 | 488 | 1825 | 2026-09-09 08:56:00 | 258 |
| 7477 | 1404 | 1749 | NULL | 2026-01-10 09:03:00 | 289 |
| 7530 | 1533 | 3010 | 2231 | 2026-02-11 10:10:00 | 320 |
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
Expected output
| view_id | user_id | username |
|---|---|---|
| 7053 | 1361 | bella_q |
| 7106 | 2622 | derek |
| 7159 | 3883 | felix |
| 7212 | 5144 | haruki |
| 7265 | 6405 | jolene |
Worked solution and explanation
What this really is
This is a lesson in what an equality match does with a missing key, dressed up as a dashboard bug. The query is one line; the skill being probed is whether you can explain why content_views holds 200 rows and the result holds 172. Every one of the 28 missing views has a NULL user_id, and a comparison against NULL is never true, so the match quietly discards them. The trap is treating the gap as a defect and 'fixing' it with a LEFT JOIN: the count comes back to 200, but 28 anonymous views now pose as user views with a blank username, and the real finding (views logged without a user) vanishes.
Step 1: Match each view to its user
Pair content_views with users on cv.user_id = u.user_id and keep view_id, user_id and username. users.user_id is the key of users, so each view matches at most one account and nothing fans out.
Step 2: Count what came back
The result has 172 rows against 200 in content_views. Before blaming the query, find the missing rows: the base table filtered on user_id IS NULL returns exactly 28.
Step 3: Rule out orphaned keys
A view can drop for two reasons: a NULL key, or a key with no users row. Here every non-null user_id exists in users, so the NULLs account for the whole gap. Saying that out loud turns a guess into a diagnosis.
SELECT cv.view_id, cv.user_id, u.username
FROM content_views cv
JOIN users u ON cv.user_id = u.user_id- Matched views only
JOIN users u ON cv.user_id = u.user_idreturns 172 rows, each a view by a known account with a realusername.- Every view, matched or not
LEFT JOIN users u ON cv.user_id = u.user_idreturns 200 rows, 28 of them withusernameNULL. That is right for a report of all traffic and wrong here, because those rows are not user views.
Common follow-up questions
- How would you report the views with no user separately, next to the matched count? (Tests surfacing the `NULL`-key rows on purpose, with a filter on `user_id IS NULL` or a conditional count, instead of hiding them.)
- What changes if some `user_id` values in `content_views` point to accounts deleted from `users`? (Tests telling orphaned keys apart from missing keys: an anti-match against `users` finds them, and the inner match drops them too.)
- If `users` had duplicate `user_id` rows, what would happen to the view count? (Tests join fan-out: a duplicated key multiplies the matching views, so the count could exceed the base table.)