SQL alert recipes
Working queries for reconciliation, drift, freshness, spikes and per-subject volume.
Real queries, in the shape AccelerUp expects. Adapt the table names.
Freshness — is the mirror still replicating?
The single most valuable rule to have, because it makes every other rule trustworthy.
SELECT
dateDiff('minute', max(_ingested_at), now()) AS mirror_lag_min
FROM warehouse.ordersCondition: mirror_lag_min outside 1 … 60, severity high.
A lag of 0 is as suspicious as one of 900 — it usually means the clock or
the query is wrong, not that replication is perfect. A corridor catches both.
Mark this condition a guard on any rule that reads the same table, and a stale mirror will report "the data is 4 hours old" rather than "failures dropped to zero".
Reconciliation — do two systems agree?
SELECT
concat('recon/', toString(day)) AS subject,
ours,
theirs,
abs(ours - theirs) AS gap,
round(100.0 * abs(ours - theirs) / nullIf(theirs, 0), 3) AS gap_pct
FROM (
SELECT
toDate(created_at) AS day,
countIf(source = 'internal') AS ours,
countIf(source = 'provider') AS theirs
FROM warehouse.payment_events
WHERE created_at > now() - INTERVAL 3 DAY
GROUP BY day
)Group by subject. Condition: gap_pct gt 0.1, warn at 0.05.
Keys rotate daily — AccelerUp resolves a firing subject that stops appearing, so yesterday's alert does not stay open forever.
Drift — is volume where it should be by now?
SELECT
subject,
formatDateTime(toStartOfDay(now()), '%Y-%m-%d 00:00-%H:00') AS measured_window,
actual_so_far,
expected_so_far,
round(100.0 * actual_so_far / nullIf(expected_so_far, 0), 1) AS pct_of_expected
FROM warehouse.volume_expectationGroup by subject. Condition: actual_so_far, mode ratio of
expected_so_far, lt 70, unit %.
The alert then says "412 against an expected 900 over 2026-09-14 00:00-10:00" — both numbers and the window, because a percentage alone sends the reader to a dashboard before they can judge it.
Spike or drop, decided per row
SELECT
segment AS subject,
drift_sigma,
multiIf(observed > expected, 'spike',
observed < expected, 'drop',
'flat') AS anomaly_kind,
formatDateTime(now(), '%Y-%m-%d %H:00') AS measured_window
FROM warehouse.segment_drift
WHERE abs(drift_sigma) > 1Group by subject. Condition: drift_sigma with direction column
anomaly_kind:
| Direction | Condition |
|---|---|
spike | gt 2.5, severity high |
drop | lt -2.5, severity critical |
flat | not alertable |
See Directions.
Error rate with a volume guard
A percentage computed from three requests is not a percentage.
SELECT
count() AS requests, -- guard
countIf(status >= 500) AS errors,
round(100.0 * countIf(status >= 500) / count(), 2) AS error_pct
FROM warehouse.http_events
WHERE ts > now() - INTERVAL 15 MINUTEConditions:
| Column | Condition | Kind |
|---|---|---|
requests | gte 100 | guard |
error_pct | gt 2, warn at 1 | measurement |
Below a hundred requests the guard fails, the measurement is not reported, and nobody is paged about a 33% error rate from one failed health check.
Per-subject volume, rolled up into one message
SELECT
subject,
count() AS volume
FROM warehouse.mail_message_logs
WHERE created_at > now() - INTERVAL 1 HOUR
GROUP BY subject
HAVING volume > 0Group by subject, notify mode summary, condition volume
lt 50.
Forty subjects breaching produce one message listing them, while each keeps its own state and recovers independently. See Notifications.
Batch-window rule
A reconciliation that only means anything after the nightly load:
- Run every 30 minutes
- Run at on the clock, offset 15
- Run window 03:00 → 07:00,
Asia/Baku
The query is not run outside the window at all — so the warehouse is not asked three hundred pointless questions a day, and the rule cannot fire at 04:10 against a half-loaded table. See Schedules.
Part of Data warehouse — Querying the warehouse, and alerting on what the query returns.