Alert Hotspots by Service and Severity

An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
easy
Seniority
L3
Asked in
City and County of San Francisco

Problem

Find how many alerts each service raised at each severity level in our alert_events log, noisiest pairings first. Severities were typed in by hand, so 'Critical' and 'critical' are the same level.

Some services and severities light up more than others.

Tables

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 16 rows
svc_nameseverityalert_count
gatewaycritical18
payment-apihigh18
auth-svccritical16
search-apilow10
user-svcmedium10

Worked solution and explanation

This is a two-key count in an on-call costume, and the real test is whether you read the data before you trust the labels. Anyone can count per svc_name and severity. What separates candidates is noticing that severity was typed by hand: 'Critical' sits next to 'critical', 'HIGH' next to 'high'. Group the raw label and you get 24 rows where every critical and high service shows up twice at 8, while the real hotspots, 'payment-api' high and 'gateway' critical at 18 alerts each, never make the list.

Getting to the answer

Step 1: Read the labels before you group

A quick look at the values in severity returns six spellings for four levels. That makes the grouping key LOWER(severity), not the column. This one function is the difference between 16 honest rows and 24 split ones.

Step 2: Group by service and the folded level

The grain is one row per service per level, so both svc_name and LOWER(severity) go in the GROUP BY. Group on the expression rather than the alias: some engines resolve a bare severity in GROUP BY to the raw column and quietly bring the split back.

Step 3: Count every alert

COUNT(*) with no WHERE. A silenced or resolved alert still paged someone, so a status filter undercounts exactly the noise the team is trying to measure.

Step 4: Sort by the count alone

ORDER BY alert_count DESC is the whole order the question asks for. Six pairings tie at 16 and any order among them is correct, so a tiebreaker adds nothing.

Alert counts per service and severity level
SELECT
  svc_name,
  LOWER(severity) AS severity,
  COUNT(*) AS alert_count
FROM alert_events
GROUP BY svc_name, LOWER(severity)
ORDER BY alert_count DESC
Raw label
GROUP BY svc_name, severity returns 24 rows. 'gateway' shows 'Critical' at 10 and 'critical' at 8, and the top of the list is a four-way tie at 10.
Folded label
GROUP BY svc_name, LOWER(severity) returns 16 rows. 'gateway' critical is 18, tied with 'payment-api' high as the noisiest pairing.

Common follow-up questions