Regions by Alert Volume
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
We're building an incident heatmap by region, and on-call wants the noisiest regions surfaced first. Count the alerts per region (based on the service's region), from the most incidents down to the fewest, and when two regions carry the same count list them alphabetically.
Some regions are quiet. Others never stop screaming.
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 |
svc_health
Columns: check_id INTEGER, svc_name TEXT, status TEXT, latency REAL, uptime REAL, checked TEXT, region TEXT
| check_id | svc_name | status | latency | uptime | checked | region |
|---|---|---|---|---|---|---|
| 137 | payment-api | degraded | 3.6 | 97.03 | 2026-02-02 01:05:00 | us-west-2 |
| 174 | user-svc | unhealthy | 6.7 | 94.06 | 2026-03-03 02:10:00 | eu-west-1 |
| 211 | search-api | timeout | 9.8 | 91.09 | 2026-04-04 03:15:00 | ap-south-1 |
| 248 | gateway | Healthy | 12.9 | 98.12 | 2026-05-05 04:20:00 | eu-central-1 |
| 285 | notif-svc | DEGRADED | 16 | 95.15 | 2026-06-06 05:25:00 | us-east-1 |
| 322 | ml-serving | healthy | 19.1 | 92.18 | 2026-07-07 06:30:00 | us-west-2 |
| 359 | cache-svc | degraded | NULL | 99.21 | 2026-08-08 07:35:00 | eu-west-1 |
| 396 | queue-worker | unhealthy | 25.3 | 96.24 | 2026-09-09 08:40:00 | ap-south-1 |
| 433 | cron-svc | timeout | 28.4 | 93.27 | 2026-01-10 09:45:00 | eu-central-1 |
| 470 | auth-svc | Healthy | 31.5 | 90.3 | 2026-02-11 10:50:00 | us-east-1 |
Expected output
| region | alert_count |
|---|---|
| us-east-1 | 688 |
| eu-west-1 | 644 |
| ap-south-1 | 468 |
| eu-central-1 | 468 |
| us-west-2 | 468 |
Worked solution and explanation
This is a dimension lookup dressed up as a heatmap. alert_events knows which service fired, never where it runs; region exists only on svc_health, so the real work is carrying that attribute over before anything gets tallied. Anyone writes the GROUP BY. What separates candidates is checking the grain of the table they borrowed from: svc_health is a log of checks, not a service directory, so each alert meets every check its service logged, and some services log from two regions. Skip that and the 688 at the top of the preview looks like a bug when the log holds only 200 alerts; 'fix' it with a dedup and you ship a different heatmap from the one on-call reads.
Getting there
Step 1: Find where region lives
alert_events carries svc_name and nothing geographic. The only region column is on svc_health, keyed by the same svc_name, so the service name is the bridge. A per-service count of alert_events is a dead end: there is nothing to roll it up by.
Step 2: Join on svc_name and accept what drops
An inner join keeps only services that have health checks. 'db-primary' and 'cache-01' fire 48 alerts between them but never appear in svc_health, so they have no region and leave the map. That is the intended behavior: a heatmap cannot place what it cannot locate.
Step 3: Name what one joined row is
After the join, a row is an alert paired with one health check of its service, not an alert. Every matched service has 18 checks: 'auth-svc' logs 10 from 'us-east-1' and 8 from 'eu-west-1', so its 24 alerts become 240 rows in one region and 192 in the other. The 152 matched alerts turn into 2,736 rows, exactly the sum of the preview.
Step 4: Tally per region, noisiest first
COUNT(*) grouped by sh.region counts those pairings. Sort by alert_count DESC, then by region: three regions tie at 468, and the alphabetical key puts 'ap-south-1' ahead of 'eu-central-1' and 'us-west-2'.
SELECT sh.region, COUNT(*) AS alert_count
FROM alert_events ae
INNER
JOIN svc_health sh ON ae.svc_name = sh.svc_name
GROUP BY sh.region
ORDER BY alert_count DESC, region- One row per health check
- Joining
svc_healthas it is weights each alert by how many checks its service logged in a region: 'us-east-1' reads 688. - One row per service and region
- Collapsing to unique
(svc_name, region)pairs first counts an alert once per region: 'us-east-1' reads 74. SameGROUP BY, different question.
Common follow-up questions
- How would you count each alert exactly once per region its service runs in? (Tests collapsing `svc_health` to unique service-region pairs before the join.)
- What if alerts whose service has no health check had to appear under an 'unknown' region? (Tests switching to an outer join and defaulting the missing `region`.)
- How would you produce the same totals without materializing the full alert-by-check join? (Tests pre-aggregating both sides by `svc_name` and multiplying the counts.)