7-Check Rolling Average
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
- L5
- Asked in
- Goldman Sachs
Problem
Our rolling latency average for a health check covers that check and the six checks before it on the same service, so one service's history never bleeds into another's. From svc_health, list every check alongside its latency and that rolling average, services alphabetically and each service's checks oldest to newest.
Seven entries hold the trend.
Tables
svc_health
Columns: check_id INTEGER, svc_name TEXT, latency REAL, checked TEXT, status TEXT, uptime REAL, 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 | checked | latency | rolling_avg |
|---|---|---|---|
| auth-svc | 2026-01-07 18:30:00 | 279.5 | 279.5 |
| auth-svc | 2026-01-08 21:03:00 | NULL | 279.5 |
| auth-svc | 2026-02-11 10:50:00 | 31.5 | 155.5 |
| auth-svc | 2026-02-17 04:20:00 | 310.5 | 207.16666666666666 |
| auth-svc | 2026-02-18 19:13:00 | 570.9 | 298.1 |
Worked solution and explanation
What this really is
This is a bounded trailing average dressed up as a reliability dashboard. Everyone slices by service and orders by time; what separates candidates is the frame. Stop at AVG(latency) OVER (PARTITION BY svc_name ORDER BY checked) and the engine silently applies its default frame, RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which averages everything since the service's first check. The first seven rows per service still match, so an eyeball check passes. Every row after that is a cumulative average where an old spike never ages out. The ten long-running services have 18 checks each on this data, so more than half of their rows come back wrong.
Step 1: Restart the history per service
PARTITION BY svc_name keeps 'the six checks before' inside one service. Leave it out and a 'gateway' check averages in whatever 'cron-svc' reported a minute earlier. The numbers look plausible and mean nothing.
Step 2: Order the frame by checked
checked is ISO text ('2026-02-11 10:50:00'), so string order is time order and no cast is needed. This ordering decides which rows count as 'before'. It is separate from the final ORDER BY that lays out the report.
Step 3: Pin the frame to seven rows
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW counts physical rows: this check plus at most six earlier ones. A service's first check sees a frame of one and its second a frame of two, so early rows average what exists instead of coming back blank. That is why the first 'auth-svc' row in the preview is just its own 279.5.
Step 4: Let AVG skip the missing latencies
23 of the 200 checks have no latency. AVG ignores them and divides by the values it did see, which is why the 2026-01-08 'auth-svc' row still shows 279.5 with its own latency blank. The row stays in the output; only its value drops out of the math.
SELECT
svc_name,
checked,
latency,
AVG(latency) OVER (
PARTITION BY svc_name
ORDER BY checked
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_avg
FROM svc_health
ORDER BY svc_name, checkedThe explicit ROWS frame is the whole answer; everything else is plumbing.
- Default frame
AVG(latency) OVER (PARTITION BY svc_name ORDER BY checked)averages from the first check to the current one. Row 18 of a service averages all 18 checks, so a January spike still weighs on September.- Explicit seven-row frame
ROWS BETWEEN 6 PRECEDING AND CURRENT ROWslides. Row 18 averages rows 12 to 18, and the January spike leaves the figure once seven newer checks arrive.
Common follow-up questions
- What if the window were the last 24 hours instead of the last seven checks? (Moves from a row-count frame to a time-range frame on a real timestamp type.)
- How would you blank the average until a service has seven checks with a latency? (A `COUNT(latency)` over the same frame plus a conditional on it.)
- Two checks for one service land on the same `checked` value. Is the result still deterministic? (Adding `check_id` as a tiebreaker inside the frame ordering.)
- How would you keep this current as checks stream in? (Holding the last seven values per service as state instead of re-sorting history.)