Silent Users
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
An account counts as silent only when not a single row in search_queries belongs to it. From users, list every silent account's username and signup date, most recently joined first.
Users who have never typed a query.
Tables
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 |
search_queries
Columns: query_id INTEGER, user_id INTEGER, search_term TEXT, results_count INTEGER, clicked_result INTEGER, query_time TEXT
| query_id | user_id | search_term | results_count | clicked_result | query_time |
|---|---|---|---|---|---|
| 2071 | 100 | laptop deals | 17 | 2 | 2026-02-02 01:11:00 |
| 2142 | 197 | phone case iphone 15 | 34 | 3 | 2026-03-03 02:22:00 |
| 2213 | 294 | usb-c cable 6ft | 51 | NULL | 2026-04-04 03:33:00 |
| 2284 | NULL | gaming mouse | 68 | 5 | 2026-05-05 04:44:00 |
| 2355 | 488 | mechanical keyboard | 85 | 6 | 2026-06-06 05:55:00 |
| 2426 | 585 | 4k monitor | 102 | NULL | 2026-07-07 06:06:00 |
| 2497 | 682 | standing desk | 119 | 8 | 2026-08-08 07:17:00 |
| 2568 | NULL | webcam 1080p | 136 | 9 | 2026-09-09 08:28:00 |
| 2639 | 876 | bluetooth speaker | 153 | NULL | 2026-01-10 09:39:00 |
| 2710 | 973 | smart watch | 170 | 1 | 2026-02-11 10:50:00 |
Expected output
| username | signup_date |
|---|---|
| viola_q | 2026-09-12 |
| helena | 2026-09-12 |
| aiden | 2026-09-09 14:53:00 |
| francisco | 2026-09-05 |
| leticia | 2026-09-01 |
Worked solution and explanation
This is a set difference dressed up as an engagement audit: every account in users minus every account that appears in search_queries. Anyone can say that out loud. What separates candidates is noticing that search_queries.user_id is blank on a quarter of the rows (50 of 200 here). Write the difference as user_id NOT IN (SELECT user_id FROM search_queries) and a single NULL in that list makes every comparison unknown. The audit then comes back empty and reports full engagement, when in fact 125 accounts have never searched.
Three moves to the answer
Step 1: Keep every account with a LEFT JOIN
Start from users and left-join search_queries on user_id. Every account survives the join. An account with no searches comes through once, with every search_queries column NULL. An inner join would discard exactly the rows the audit is looking for.
Step 2: Keep only the rows the join could not match
Filter with WHERE sq.query_id IS NULL. query_id is never NULL in the table, so a NULL there can only mean the account had no match. If you test a nullable column like clicked_result instead, you also sweep in active users whose searches never got a click.
Step 3: Sort newest signup first
ORDER BY u.signup_date DESC is the only ordering the audit asks for. Two accounts that signed up on the same day can come back in either order.
SELECT u.username, u.signup_date
FROM users AS u
LEFT JOIN search_queries AS sq
ON sq.user_id = u.user_id
WHERE sq.query_id IS NULL
ORDER BY u.signup_date DESC- The membership test
WHERE user_id NOT IN (SELECT user_id FROM search_queries)reads like the requirement word for word. ButNOT INmeans 'differs from every value in the list', and a comparison with NULL is never true. With 50 blankuser_idvalues on file, it returns zero rows.- The unmatched-row filter
LEFT JOIN search_queriesfollowed byWHERE sq.query_id IS NULLasks a different question: did this account match any search row at all? A blankuser_idmatches nobody, so it never affects the result, and all 125 silent accounts come back.
Common follow-up questions
- Silent now means no search in the last 90 days of `query_time`. Where does that condition go so that long-dormant users are still counted? (Tests whether the date filter goes into the join condition rather than the `WHERE`, which would quietly turn the query back into an inner join.)
- Rewrite it with `NOT EXISTS`. Does the NULL `user_id` problem come back? (Tests whether the candidate understands that a correlated `EXISTS` compares row by row and is NULL-safe.)
- Two accounts have a `signup_date` that includes a time of day. How would you sort by calendar day only? (Probes noticing mixed date and timestamp text in the same column.)