Service Uptime Minutes

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
L5
Asked in
Samsara

Problem

Find how many full hours each service's still-open alerts in alert_events have been firing as of 2026-09-12, where every alert counts only its own completed hours before they are added up. The reliability team's onboarding review covers alerts that fired no later than 180 days after the service's earliest check in svc_health.

Status changed. How long was it actually up?

Tables

svc_health

Columns: check_id INTEGER, svc_name TEXT, checked TEXT, status TEXT, latency REAL, uptime REAL, region TEXT

svc_health: 10 of 200 rows
check_idsvc_namestatuslatencyuptimecheckedregion
137payment-apidegraded3.697.032026-02-02 01:05:00us-west-2
174user-svcunhealthy6.794.062026-03-03 02:10:00eu-west-1
211search-apitimeout9.891.092026-04-04 03:15:00ap-south-1
248gatewayHealthy12.998.122026-05-05 04:20:00eu-central-1
285notif-svcDEGRADED1695.152026-06-06 05:25:00us-east-1
322ml-servinghealthy19.192.182026-07-07 06:30:00us-west-2
359cache-svcdegradedNULL99.212026-08-08 07:35:00eu-west-1
396queue-workerunhealthy25.396.242026-09-09 08:40:00ap-south-1
433cron-svctimeout28.493.272026-01-10 09:45:00eu-central-1
470auth-svcHealthy31.590.32026-02-11 10:50:00us-east-1

alert_events

Columns: alert_id INTEGER, svc_name TEXT, fired_at TEXT, resolved TEXT, severity TEXT, status TEXT, ack_by 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_nameopen_alert_hours
auth-svc87402
gateway57022
notif-svc75230
search-api91644
user-svc65698

Worked solution and explanation

What this really is

This is an open-interval measurement dressed up as an uptime report. The missing resolved timestamp is not dirty data to clean away: it IS the open alert, the only row the question is about, and its end point is not in the table at all. Anyone can write the per-service MIN(checked) and the join. What separates candidates is whether muscle memory types resolved IS NOT NULL and measures fired-to-resolved, or drops the test and measures everything to the as-of date. Either way every service's total is wrong, the second one by months of hours from incidents fixed long ago.


Building it

Step 1: Anchor each service on its own first check

The review period starts at each service's earliest row in svc_health, so the svc_start CTE takes MIN(checked) per svc_name. A single fleet-wide start date would let late-onboarded services drag in alerts from well past their first 180 days.

Step 2: Inner-join alerts to the anchor

Join alert_events to svc_start on svc_name. Services that alert but were never health-checked (the seed has a few, like 'db-primary') have no period to measure against, and the inner join drops them on purpose.

Step 3: Keep only the open alerts

a.resolved IS NULL is the whole point of the query. It throws away roughly 94% of the alert rows, every one a closed incident the question does not ask about.

Step 4: Cut at 180 days past the anchor

An open alert has no resolution to test, so the window is on fired_at: keep it when a.fired_at <= datetime(s.first_check, '+180 days'). Alerts raised later in the service's life are outside the onboarding review.

Step 5: Measure to the as-of date, then truncate per alert

julianday('2026-09-12') - julianday(a.fired_at) is days elapsed; times 24 is hours. The CAST to INTEGER sits inside the SUM, so each alert drops its partial hour before the service total is built.

Open-alert hours per service
WITH svc_start AS (
    SELECT svc_name, MIN(checked) AS first_check
    FROM svc_health
    GROUP BY svc_name
)
SELECT a.svc_name,
       SUM(CAST((julianday('2026-09-12') - julianday(a.fired_at)) * 24 AS INTEGER)) AS open_alert_hours
FROM alert_events a
JOIN svc_start s ON a.svc_name = s.svc_name
WHERE a.resolved IS NULL
  AND a.fired_at <= datetime(s.first_check, '+180 days')
GROUP BY a.svc_name
Habit
WHERE resolved IS NOT NULL and resolved - fired_at: a perfectly good closed-incident report, answering a question nobody asked.
The question
WHERE resolved IS NULL and 2026-09-12 - fired_at: only the alerts still burning, measured to the moment of the review.

Common follow-up questions