Daily Error Resolution Ratio

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
Asked in
Meta

Problem

For every day on which err_tracks logged at least one error more serious than informational, report how many unique services were hit, how many alerts in alert_events fired that day and were eventually cleared, and the ratio of cleared alerts to services hit as a decimal rounded to two places. The reliability team reads it as a daily health check, so an error day where nothing cleared still appears, with a zero.

Reported versus removed. The daily ratio.

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 156 rows
report_datereported_servicesresolved_alertsresolution_ratio
2026-01-03100
2026-01-04100
2026-01-17111
2026-02-01111
2026-02-10122

Worked solution and explanation

What this really is

Strip the ops costume and this is a two-series daily alignment: one series is how many unique services threw a real error each day, the other is how many alerts fired and genuinely cleared that same day. Different tables, different filters, one shared calendar-day grain. Anyone can write the two aggregates. What separates candidates is combining them so the error days with nothing cleared stay in the report, counting services rather than error rows, and keeping the ratio a real fraction. Miss any one and the report hides exactly the backlog on-call needs to see.


Step 1: Count unique affected services per day

Bucket err_tracks by date(first_at) and take COUNT(DISTINCT svc_name). Unique matters because one service can throw the same error dozens of times in a day; COUNT(*) would report a fleet-wide meltdown from a single noisy service. Drop severity 'info' first, since an informational line is not an incident.

Step 2: Count the alerts that cleared that day

On alert_events, keep only rows where resolved IS NOT NULL, then bucket by date(fired_at) and count. The resolved timestamp is the signal, not the status text: a row can read 'resolved' while its resolved column is still null, and that alert never cleared. Each alert is a discrete incident, so a straight COUNT(*) is right here.

Step 3: Left join from the error side and take the ratio

Drive the join from affected and LEFT JOIN resolutions, so a day with no cleared alert still produces a row. COALESCE the missing count to 0, CAST it to REAL, and divide by that day's service count, looked up with a correlated scalar subquery on report_date. ROUND(..., 2) gives the two-decimal ratio the report asks for.


The solution

Daily reported-to-resolved ratio
WITH affected AS (
    SELECT date(first_at) AS report_date,
           COUNT(DISTINCT svc_name) AS reported_services
    FROM err_tracks
    WHERE severity <> 'info'
    GROUP BY date(first_at)
),
resolutions AS (
    SELECT date(fired_at) AS event_date,
           COUNT(*) AS resolved_alerts
    FROM alert_events
    WHERE resolved IS NOT NULL
    GROUP BY date(fired_at)
)
SELECT
    a.report_date,
    a.reported_services,
    COALESCE(res.resolved_alerts, 0) AS resolved_alerts,
    ROUND(
        CAST(COALESCE(res.resolved_alerts, 0) AS REAL)
        / (SELECT a2.reported_services FROM affected a2 WHERE a2.report_date = a.report_date),
        2
    ) AS resolution_ratio
FROM affected a
LEFT JOIN resolutions res ON res.event_date = a.report_date

Two daily CTEs, left joined from the error side so zero-clear days survive.

Inner join (wrong)
Only days that had both an error and a cleared alert appear. Quiet-but-broken days vanish and the fleet looks calmer than it is.
Left join (right)
Every error day appears; days with nothing cleared show 0 and a 0.0 ratio, surfacing exactly the backlog on-call cares about.

Common follow-up questions