Biggest Deployment Decline
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
An on-call engineer tracing a surge in release cadence is comparing each service's deploys in deploy_logs for 2026 against 2025, counting every logged deploy whatever its status, but only for services that svc_health monitors under exactly the same name. Return the single service whose deploy count grew the most, along with how many more deploys it logged in 2026.
One team's deploy count cratered. Which one?
Tables
deploy_logs
Columns: log_id INTEGER, svc_name TEXT, version TEXT, env_name TEXT, status TEXT, deploy_at TEXT, dur_secs INTEGER, author TEXT
| log_id | svc_name | version | env_name | status | deploy_at | dur_secs | author |
|---|---|---|---|---|---|---|---|
| 131 | payment-api | v1.0.2 | staging | failed | 2026-02-02 01:11:00 | 23 | bob |
| 162 | user-svc | v1.1.0 | dev | rolled_back | 2026-03-03 02:22:00 | 36 | charlie |
| 193 | search-api | 2.0.0 | canary | in_progress | 2026-04-04 03:33:00 | 49 | dana |
| 224 | gateway | v2.1.0 | Production | Success | 2026-05-05 04:44:00 | 62 | eve |
| 255 | notif-svc | 2.1.1 | STAGING | FAILED | 2026-06-06 05:55:00 | 75 | frank |
| 286 | analytics | v3.0.0-rc1 | production | success | 2026-07-07 06:06:00 | 88 | Alice |
| 317 | ml-serving | v1.0.3 | staging | failed | 2026-08-08 07:17:00 | NULL | BOB |
| 348 | auth-svc | v1.0.1 | dev | Rolled_Back | 2026-09-09 08:28:00 | 114 | alice |
| 379 | Payment-API | v1.0.2 | canary | in_progress | 2026-01-10 09:39:00 | 127 | bob |
| 410 | user-svc | v1.1.0 | Production | Success | 2026-02-11 10:50:00 | 140 | charlie |
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
| svc_name | deploy_growth |
|---|---|
| user-svc | 14 |
Worked solution and explanation
This is a year-over-year delta wearing an on-call costume. Anyone can count deploys per service. What separates candidates is two quiet decisions. First, svc_health is a log of checks, not a registry, so joining it raw copies every deploy once per health check and a gain of twelve reads as a gain in the hundreds. Second, once the rows are fenced to 2025 and 2026, every deploy is either a this-year gain or a last-year loss, so the whole change is one signed tally. Skip the fence and three older years of history pour in as losses; sort the wrong way and the service that barely moved wins a contest about acceleration.
Build it in four moves
Step 1: Fence the rows to the two years
WHERE EXTRACT(YEAR FROM deploy_at) IN (2025, 2026). This is not housekeeping: deploy_logs reaches back to 2022, and the tally in step 3 treats anything that is not this year as last year. The filter is the only thing that makes that true.
Step 2: Turn svc_health into a set of names
SELECT DISTINCT svc_name inside a derived table collapses the checks to one row per monitored service, and the inner join keeps only deploys for those names. Off-spellings in deploy_logs such as 'Payment-API' have no exact match and fall away, which is what "monitored under exactly the same name" asks for.
Step 3: Tally signed in one pass
SUM(CASE WHEN year = 2026 THEN 1 ELSE -1 END) per service is this year minus last year directly. A service that deployed in only one of the two years still gets the right number, with no outer join and no COALESCE.
Step 4: Sort by direction, break the tie, take one
ORDER BY deploy_growth DESC, svc_name then LIMIT 1. Descending on the signed value puts the biggest gain first; the alphabetical key only matters if two services grew by the same amount, and then it decides which one the report names.
SELECT
dl.svc_name AS svc_name,
SUM(CASE WHEN EXTRACT(YEAR FROM dl.deploy_at) = 2026 THEN 1 ELSE -1 END) AS deploy_growth
FROM deploy_logs dl
JOIN (
SELECT DISTINCT svc_name
FROM svc_health
) monitored
ON monitored.svc_name = dl.svc_name
WHERE EXTRACT(YEAR FROM dl.deploy_at) IN (2025, 2026)
GROUP BY dl.svc_name
ORDER BY deploy_growth DESC LIMIT 1;- Two filtered sums
SUM(CASE WHEN year = 2026 ...)minusSUM(CASE WHEN year = 2025 ...)gives the same numbers, but each sum filters its own year, so it never needs the window and invites you to drop it. Then the scan covers all five years of history for nothing.- One signed tally
WHEREfences the window once, andCASE ... THEN 1 ELSE -1reads the direction off each row. One expression, one pass, and the filter is visibly load-bearing.
Common follow-up questions
- Return the top 5 growers with their percentage change, not just the count change. (Needs a separate last-year baseline and a guard against dividing by a zero count.)
- 2026 is only partly over. How would you compare it fairly against a full 2025? (Cap last year at the same day of year, or compare deploys per elapsed month.)
- What changes if a service must appear in `svc_health` during the two years specifically? (Moves the membership check into a date-bounded set instead of a global `DISTINCT`.)
- How would you fold 'Payment-API' and ' payment-api' into the monitored service instead of dropping them? (Normalizing the name with `TRIM` and `LOWER` on both sides before the join.)