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_tracks: 10 of 200 rows
err_iderr_typemessagesvc_nameseveritycountfirst_at
137TypeErrorCannot read property of undefinedpayment-apierror82026-02-02 01:11:00
174ConnectionTimeoutConnection timed out after 30suser-svcwarning152026-03-03 02:22:00
211OutOfMemoryHeap space exhaustedsearch-apiinfo222026-04-04 03:33:00
248FileNotFoundFile /tmp/data.csv not foundgatewayFatal292026-05-05 04:44:00
285PermissionDeniedAccess denied for user rootworkerERROR362026-06-06 05:55:00
322RateLimitExceededToo many requests from 10.0.0.1auth-svcfatal432026-07-07 06:06:00
359ParseErrorMalformed JSON in request bodypayment-apierror502026-08-08 07:17:00
396nullpointerexceptionStack overflow in recursive calluser-svcwarning572026-09-09 08:28:00
433TYPEERRORDivision by zerosearch-apiinfo642026-01-10 09:39:00
470NullPointerExceptionUnexpected null value at line 42gatewayFatalNULL2026-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_events: 10 of 200 rows
alert_idsvc_nameseveritystatusfired_atack_byresolved
129payment-apihighacknowledged2026-02-02 01:07:00bob2026-02-02 03:11:00
158user-svcmediumresolved2026-03-03 02:14:00charlie2026-03-03 04:22:00
187search-apilowsilenced2026-04-04 03:21:00danaNULL
216gatewayCriticalfiring2026-05-05 04:28:00NULL2026-05-05 06:44:00
245notif-svcHIGHacknowledged2026-06-06 05:35:00alice2026-06-06 07:55:00
274db-primarycriticalresolved2026-07-07 06:42:00bobNULL
303cache-01highsilenced2026-08-08 07:49:00charlie2026-08-08 09:17:00
332auth-svcmediumfiring2026-09-09 08:56:00NULL2026-09-09 10:28:00
361payment-apilowacknowledged2026-01-10 09:03:00eveNULL
390user-svcCriticalresolved2026-02-11 10:10:00alice2026-02-11 12:50:00

Expected output

First 5 of 5 rows
svc_nametotal_errors
payment-api32198
search-api31379
user-svc31308
auth-svc30771
gateway27643

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.

Watched services by total error volume
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_events repeats 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