The Ones We Watch
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
The SRE team's error review keeps surfacing noisy services that nobody has an alert on, so it now covers only services with at least one alert in alert_events. Total the error occurrences each of those services has logged in err_tracks and list them from noisiest to quietest.
Only the services still under watch count. Rank them by where the errors gather.
Tables
err_tracks
Columns: err_id INTEGER, err_type TEXT, message TEXT, svc_name TEXT, severity TEXT, count INTEGER, first_at TEXT
| err_id | err_type | message | svc_name | severity | count | first_at |
|---|---|---|---|---|---|---|
| 137 | TypeError | Cannot read property of undefined | payment-api | error | 8 | 2026-02-02 01:11:00 |
| 174 | ConnectionTimeout | Connection timed out after 30s | user-svc | warning | 15 | 2026-03-03 02:22:00 |
| 211 | OutOfMemory | Heap space exhausted | search-api | info | 22 | 2026-04-04 03:33:00 |
| 248 | FileNotFound | File /tmp/data.csv not found | gateway | Fatal | 29 | 2026-05-05 04:44:00 |
| 285 | PermissionDenied | Access denied for user root | worker | ERROR | 36 | 2026-06-06 05:55:00 |
| 322 | RateLimitExceeded | Too many requests from 10.0.0.1 | auth-svc | fatal | 43 | 2026-07-07 06:06:00 |
| 359 | ParseError | Malformed JSON in request body | payment-api | error | 50 | 2026-08-08 07:17:00 |
| 396 | nullpointerexception | Stack overflow in recursive call | user-svc | warning | 57 | 2026-09-09 08:28:00 |
| 433 | TYPEERROR | Division by zero | search-api | info | 64 | 2026-01-10 09:39:00 |
| 470 | NullPointerException | Unexpected null value at line 42 | gateway | Fatal | NULL | 2026-02-11 10:50:00 |
alert_events
Columns: alert_id INTEGER, svc_name TEXT, severity TEXT, status TEXT, fired_at TEXT, ack_by TEXT, resolved TEXT
| alert_id | svc_name | severity | status | fired_at | ack_by | resolved |
|---|---|---|---|---|---|---|
| 129 | payment-api | high | acknowledged | 2026-02-02 01:07:00 | bob | 2026-02-02 03:11:00 |
| 158 | user-svc | medium | resolved | 2026-03-03 02:14:00 | charlie | 2026-03-03 04:22:00 |
| 187 | search-api | low | silenced | 2026-04-04 03:21:00 | dana | NULL |
| 216 | gateway | Critical | firing | 2026-05-05 04:28:00 | NULL | 2026-05-05 06:44:00 |
| 245 | notif-svc | HIGH | acknowledged | 2026-06-06 05:35:00 | alice | 2026-06-06 07:55:00 |
| 274 | db-primary | critical | resolved | 2026-07-07 06:42:00 | bob | NULL |
| 303 | cache-01 | high | silenced | 2026-08-08 07:49:00 | charlie | 2026-08-08 09:17:00 |
| 332 | auth-svc | medium | firing | 2026-09-09 08:56:00 | NULL | 2026-09-09 10:28:00 |
| 361 | payment-api | low | acknowledged | 2026-01-10 09:03:00 | eve | NULL |
| 390 | user-svc | Critical | resolved | 2026-02-11 10:10:00 | alice | 2026-02-11 12:50:00 |
Expected output
| svc_name | total_errors |
|---|---|
| payment-api | 32198 |
| search-api | 31379 |
| user-svc | 31308 |
| auth-svc | 30771 |
| gateway | 27643 |
Worked solution and explanation
Strip the costume
Under the observability story this is a semi-join feeding a grouped SUM. The skill is to scope err_tracks to the services that have an alert in alert_events, then total each one's occurrences without letting the second table multiply anything. Anyone can sum a column. What separates candidates is reading 'has an alert' as a yes-or-no membership test instead of a join, and summing the count column instead of counting rows. Join instead and every total comes back about 25 times too big. Skip the scope and 'worker' walks into the standings.
The scope is a membership test
A service is in scope if alert_events holds at least one row for it. That is a question of existence, not a row multiplier, so express it as a membership test (IN a subquery, or EXISTS). The test exists because of 'worker'. It logs 30,777 occurrences in err_tracks but has no alert anywhere. Without the scope it slots in between 'user-svc' and 'auth-svc' and looks exactly like a watched service.
Total occurrences, not rows
Each err_tracks row is one error type with a count of how many times it fired, so 'occurrences' means SUM(count), not the number of rows. COUNT(*) flattens every watched service to 33 or 34 rows, and the ordering turns into noise. Eighteen rows have a NULL count, six of them on 'gateway'. SUM skips them, so 'gateway' lands at 27,643 instead of failing or zeroing out.
SELECT e.svc_name AS svc_name,
SUM(e.count) AS total_errors
FROM err_tracks e
WHERE e.svc_name IN (
SELECT a.svc_name FROM alert_events a
)
GROUP BY e.svc_name
ORDER BY total_errors DESC;The IN subquery scopes to monitored services without fan-out; SUM totals real occurrences and skips the NULLs; the sort puts the noisiest first.
Step 1: Scope to watched services
Build the in-scope set: every svc_name in alert_events, which is 'payment-api', 'user-svc', 'search-api', 'gateway', 'notif-svc', 'db-primary', 'cache-01' and 'auth-svc'. 'worker' appears only in err_tracks, and dropping it is the whole point of the scope.
Step 2: Total the occurrences
Group the surviving err_tracks rows by svc_name and sum count: 'payment-api' 32,198, 'search-api' 31,379, 'user-svc' 31,308, 'auth-svc' 30,771, 'gateway' 27,643. 'notif-svc', 'db-primary' and 'cache-01' are in scope but log no errors, so they never appear.
Step 3: Order most to least
Sort by total_errors descending so the noisiest watched service leads. The five totals are all different, so no tie rule is needed and 'payment-api' sits on top.
- Naive: join, then sum
- Joining
alert_eventsrepeats each error row once per alert, so every total is inflated about 25 times over. Dropping the scope instead lets 'worker' and its 30,777 occurrences into the list. Either way the numbers are wrong. - Correct: membership test, then sum
IN (SELECT svc_name FROM alert_events)scopes without duplicating rows,SUM(count)totals real occurrences and skips the NULLs, and 'worker' stays out.
Common follow-up questions
- Now return only the single busiest watched service, but if several tie for the top total, return all of them. How does the query change? (Pushes them from a plain sort toward a ranking function and away from a blind LIMIT.)
- Include watched services that logged zero errors, showing them with a total of 0. What do you change? (Tests an outer join from the scope set with COALESCE instead of an inner membership filter.)
- Break the totals out by `severity` while normalizing casing like 'ERROR' and 'error' into one bucket. What do you add? (Extends the grouping and forces case normalization on a dirty column.)