Service Scorecard

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

Problem

Services that never fired an alert still belong on the on-call lead's scorecard, with an alert count of zero. For every service with pods in pod_assignments, she wants how many pod assignments it has and how many alerts in alert_events fired for it.

Deploys vs. alerts. One row per service tells the whole story.

Tables

pod_assignments

Columns: assignment_id INTEGER, node_id INTEGER, pod_id INTEGER, svc_name TEXT, assigned_at TEXT, status TEXT

pod_assignments: 10 of 200 rows
assignment_idnode_idpod_idsvc_nameassigned_atstatus
804711482344payment-api2026-02-02 01:11:00draining
809412592645user-svc2026-03-03 02:22:00pending
814113702946search-api2026-04-04 03:33:00terminated
818814813247gateway2026-05-05 04:44:00Active
823515923548notif-svc2026-06-06 05:55:00ACTIVE
828217033849analytics2026-07-07 06:06:00active
832918144150ml-serving2026-08-08 07:17:00draining
837619254451auth-svc2026-09-09 08:28:00pending
842320364752payment-api2026-01-10 09:39:00terminated
847021475053user-svc2026-02-11 10:50:00Active

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 8 rows
svc_nameassignment_countalert_count
analytics240
auth-svc2424
gateway2626
ml-serving240
payment-api2626

Worked solution and explanation

This is two independent one-to-many relationships hanging off svc_name, dressed up as an on-call scorecard. A service has dozens of rows in pod_assignments and, if it alerts at all, 24 or 26 in alert_events. The two tables share nothing but the name. Anyone can write the join. What separates candidates is collapsing the assignments to one row per service before the alerts touch them, and counting alert_id rather than rows. Join the raw tables and both columns become assignments times alerts. Count rows and analytics reports an alert it never fired.

Getting to the answer

Step 1: Collapse pod_assignments to one row per service

The assignment_counts CTE groups pod_assignments by svc_name with COUNT(*). After this, a service's assignment total is one number on one row, and nothing downstream can multiply it.

Step 2: Attach alerts to that row with a LEFT JOIN

Joining alert_events to the per-service row emits one row per alert for an alerting service, and one null-padded row for analytics and ml-serving, which have pods but no alerts. An inner join would silently drop those two.

Step 3: Count alert.alert_id, not *

COUNT(alert.alert_id) skips the null on a quiet service's padded row, so it scores 0. Grouping by svc.assignment_count alongside svc.svc_name costs nothing, because the count is constant within a service.

Per-service scorecard
WITH assignment_counts AS (
    SELECT svc_name, COUNT(*) AS assignment_count
    FROM pod_assignments
    GROUP BY svc_name
)
SELECT svc.svc_name, svc.assignment_count, COUNT(alert.alert_id) AS alert_count
FROM assignment_counts svc
LEFT JOIN alert_events alert ON svc.svc_name = alert.svc_name
GROUP BY svc.svc_name, svc.assignment_count
Raw join, one aggregation
Every assignment row meets every alert row for its service, so COUNT(*) reports assignments times alerts as the assignment total. There is no way back to either true number from that grain.
Aggregate first, then join
assignment_counts holds one row per service, so the join fans out only over alerts, and assignment_count rides along unchanged.
The fan-out version (wrong)
SELECT asg.svc_name,
       COUNT(*) AS assignment_count,
       COUNT(alert.alert_id) AS alert_count
FROM pod_assignments asg
LEFT JOIN alert_events alert ON asg.svc_name = alert.svc_name
GROUP BY asg.svc_name

Both counts are inflated to assignments times alerts for every alerting service.

Common follow-up questions