Alert Count by Severity Tier
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
- L3
- Asked in
- City and County of San Francisco
Problem
How many alerts did each responder pick up across all of alert_events, whatever state the alert ended in? For our on-call load review, every alert nobody picked up counts together under 'unassigned'.
Alerts by severity. The breakdown matters.
Tables
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
| responder | alert_count |
|---|---|
| alice | 30 |
| bob | 30 |
| charlie | 30 |
| dana | 30 |
| unassigned | 50 |
Worked solution and explanation
What this problem is really testing
This is a group-and-count where the missing value is a real category, dressed up as an on-call review. Anyone can count rows per ack_by; what separates candidates is noticing that 50 of the 200 alerts have no acknowledger at all, and that those alerts are the point: they are the load nobody carried. Fold them into one named 'unassigned' bucket with COALESCE and the answer is six clean rows. Group on the raw column and they land under a blank NULL key. Drop them with a filter, or count only alerts whose status is 'acknowledged', and the largest slice of on-call load quietly disappears from the review.
Break down the requirements
Step 1: Fold unacknowledged alerts into 'unassigned'
COALESCE(ack_by, 'unassigned') AS responder. An alert with no recorded acknowledger was never picked up by anyone, and the review tracks all of that unclaimed load together. Grouping on the raw column instead splits those rows into their own blank-named NULL group, which is exactly the coverage gap the review is trying to name.
Step 2: Count every alert, whatever its status
COUNT(*) on alert_events, with no filter on status. ack_by is filled on resolved and silenced alerts too; on this data only the 'firing' alerts carry no acknowledger. So the acknowledger column, not the status label, says who picked an alert up. COUNT(alert_id) gives the same number since alert_id is never null, but COUNT(*) states the intent.
Step 3: Group by the labeled responder
GROUP BY responder, so the folded rows collapse into the single 'unassigned' group. Grouping on the labeled expression, not the raw ack_by, is what keeps the unacknowledged alerts in one named bucket rather than under a nameless key.
Step 4: Return the whole distribution
No LIMIT and no date filter: the review covers every alert in the table, so all five responders and the 'unassigned' bucket come back. Row order is not part of the answer; any order of the six rows is correct.
The solution
SELECT
COALESCE(ack_by, 'unassigned') AS responder,
COUNT(*) AS alert_count
FROM alert_events
GROUP BY responderCommon follow-up questions
Common follow-up questions
- Break the same counts down by `severity` within each responder. (Adds a second grouping key and forces reasoning about grain, plus the mixed-case `severity` values that need `LOWER` first.)
- For each responder, also show how many of their alerts were eventually resolved. (Pushes toward `COUNT(*) FILTER (WHERE resolved IS NOT NULL)` or a conditional `SUM` alongside the total.)
- Same breakdown, but only for the last 24 hours. (Checks whether the candidate adds a `fired_at` predicate and recognizes that partition pruning now kicks in.)
- Add each responder's percentage of total alert volume. (Needs `COUNT(*) OVER ()` or a scalar subquery as the denominator.)