
Scheduled query alerts turn database questions into timely, actionable notifications. Instead of waiting to check dashboards, you define the conditions that matter and run them on a schedule. When they match, you get an alert. This approach is simple, reliable, and works across SQL and NoSQL systems. In this guide, you will learn when to use scheduled query alerts, how to design them for safety and speed, and how to ship clear, noise free notifications that drive action.
What are scheduled query alerts
A scheduled query alert is a read only query that runs at a fixed cadence to detect important changes. Common examples include spikes in failed logins, slow order fulfillment, inventory below threshold, or payments stuck in pending. If the query returns rows or exceeds a threshold, the alert triggers a message to the right person or channel.
Key properties of scheduled query alerts:
- They use polling on a defined interval
- They can aggregate and compare data over time windows
- They are portable across database engines with minor syntax changes
- They are easy to reason about, test, and roll back
Scheduled vs event driven: choosing the right model
Event driven approaches such as change data capture, database triggers, or change streams are great for near real time detection. However, scheduled query alerts have distinct advantages in several cases:
- You need aggregation or thresholds over time windows, not per row events
- Your source does not expose reliable change streams
- You want a single, consistent pattern that works across Postgres, MySQL, and MongoDB
- You prefer simpler operations without additional services or long lived connections
In practice, teams often combine both. Use event driven for low latency per row workflows, and scheduled query alerts for periodic checks, trend thresholds, and compliance reporting.
A reference architecture for scheduled query alerts
A minimal architecture has five parts: source database, scheduler, query runner, state store, and notifier.
[Client scheduler] --triggers--> [Query runner] --reads--> [Database]
--writes--> [State store] --feeds--> [Dedup and rules]
--sends--> [Notifier: push, email, webhook]- Scheduler runs jobs at fixed intervals
- Query runner executes read only statements over secure connections
- State store tracks last seen ids or timestamps to avoid duplicates
- Notifier delivers messages and manages retries
Setting intervals for scheduled query alerts
Cadence should reflect how quickly the risk grows and how costly it is to miss a signal. A helpful rule: as data volatility and business impact increase, shorten the interval, but keep a tight cap on resource usage.
| Scenario | Data volatility | Alert objective | Suggested interval |
|---|---|---|---|
| Fraud pattern detection | High | Minimize loss | 1 to 5 minutes |
| Order SLA breach | Medium | Meet delivery SLA | 5 to 15 minutes |
| Inventory below threshold | Low | Avoid stockouts | 15 to 60 minutes |
| Daily KPI snapshots | Low | Reporting | Hourly to daily |
Always measure and adjust. If your alert is noisy, lengthen the interval or tighten conditions. If you are missing issues, shorten the interval or add secondary checks.
Writing safe, efficient queries
Well written queries reduce load and produce reliable, deduplicated alerts.
- Use read only connections and least privilege roles
- Filter by indexed columns and time windows
- Prefer incremental scans: greater than last id or last timestamp
- Bound your scans with limits and paginate if needed
- Include enough context for action, but avoid sensitive data
Postgres example: incremental new orders
-- Assume a state store persists last_seen_id
WITH new_orders AS (
SELECT id, customer_id, total_cents, created_at
FROM orders
WHERE id > :last_seen_id
ORDER BY id ASC
LIMIT 1000
)
SELECT *
FROM new_orders
WHERE total_cents >= 5000;On success, update last_seen_id to the max id returned. This yields idempotent alerts without rescanning the table.
Guarding against time drift
If you prefer time based windows, use overlapping windows with a guard to avoid duplicates:
-- Last 7 minutes window, run every 5 minutes, dedupe with state
WITH candidates AS (
SELECT id, created_at
FROM auth_events
WHERE created_at > now() - interval '7 minutes'
),
dedup AS (
SELECT c.*
FROM candidates c
LEFT JOIN alert_sent a ON a.event_id = c.id
WHERE a.event_id IS NULL
)
SELECT * FROM dedup WHERE event_type = 'failed_login';The alert_sent table prevents duplicates even with overlap, and preserves history for audits.
MySQL example: threshold alert
SELECT COUNT(*) AS pending_count
FROM orders
WHERE status = 'pending'
AND created_at < NOW() - INTERVAL 30 MINUTE;Trigger the alert only when pending_count exceeds a threshold you set.
NoSQL patterns for scheduled query alerts
Change streams are powerful, but you can also schedule reads for stateful checks.
MongoDB example: recent failures
// Pseudocode aggregation
db.jobs.aggregate([
{ $match: {
status: 'failed',
updated_at: { $gt: last_seen_ts }
}},
{ $project: { _id: 1, job_type: 1, reason: 1, updated_at: 1 }},
{ $sort: { updated_at: 1 }},
{ $limit: 1000 }
]);Persist last_seen_ts after processing the batch. If you need a threshold, follow with a grouping stage and alert when counts exceed your limit.
Reduce noise and alert fatigue
Alerts should be about action, not awareness.
- Apply thresholds and hysteresis to avoid flapping
- Group similar rows into one message with a short summary
- Set maintenance windows and temporary suppressions for planned work
- Route by severity: critical to on call, warning to email
- Attach a runbook link with expected steps
Grouping example
SELECT service, COUNT(*) AS failures
FROM task_runs
WHERE status = 'failed'
AND finished_at > now() - interval '10 minutes'
GROUP BY service
HAVING COUNT(*) >= 5;This produces one row per noisy service rather than a flood of row level alerts.
Delivery channels and formatting
Choose channels that match urgency and audience.
| Channel | Latency | Best for | Notes |
|---|---|---|---|
| Push | Very low | On call, mobile first response | Great for urgent, short summaries |
| Low to medium | Summaries, non urgent alerts | Supports longer context and links | |
| Webhook | Low | Automation pipelines | Fan out to chat and ticketing |
Keep messages concise: title, severity, top metrics, sample ids, and a runbook link. Avoid sending full PII.
Observability for your alerting jobs
- Job schedule delay: now minus scheduled start
- Query duration and rows scanned
- Rows returned and alerts emitted
- Delivery success, retries, and time to acknowledge
- Error rate by query and by data source
Log and dashboard these metrics. Alert on the alerting system itself when jobs fall behind or delivery fails repeatedly.
Simple job log pattern
INSERT INTO alert_job_log
(job_name, started_at, finished_at, rows_scanned, rows_matched, alerts_sent, ok)
VALUES
(:job_name, now(), now() + (:dur || ' ms')::interval, :scanned, :matched, :sent, TRUE);Security and compliance
- Use least privilege database roles that can only read specific schemas or views
- Encrypt transport and store credentials in a secrets manager
- Prefer views that mask sensitive fields and expose only what alerts need
- Scrub PII from messages; include references and links instead
- Audit who defined, changed, and acknowledged alerts
View based access example
CREATE VIEW v_order_alerts AS
SELECT id, created_at, status, total_cents
FROM orders
WHERE is_sensitive = FALSE;Point your scheduled query alerts to v_order_alerts to limit data exposure.
Testing and rollout checklist
- Define success: condition, recipients, and expected action
- Dry run queries with historical windows and compare with known incidents
- Backfill the state store and run in shadow mode for a week
- Start with longer intervals and tighten over time
- Cap delivery volume and enable grouping from day one
- Document a runbook and add deep links to dashboards
- Set clear ownership for each alert
End to end playbook: pending orders SLA
- Goal: alert if pending orders older than 10 minutes exceed 20
- Query:
WITH stalled AS (
SELECT id
FROM orders
WHERE status = 'pending'
AND created_at < now() - interval '10 minutes'
)
SELECT COUNT(*) AS cnt
FROM stalled;- Trigger rule: send a warning when cnt is 15 or more. Send critical when cnt is 20 or more
- Schedule: run every 5 minutes
- Message template: service name, count, top 3 ids, link to runbook
- Rollback plan: disable the job or increase thresholds if noise appears
Cron style schedule example
*/5 * * * * run_order_sla_checkIf you use a managed scheduler, encode the same cadence with its UI or API and monitor run history.
Performance tips that matter
- Prefer narrow SELECT lists and covered indexes
- Keep windows short and scan increments, not entire tables
- Batch large result sets and stream delivery
- Cache stable lookup data to reduce joins on hot paths
- Archive or partition historical data to keep scans tight
Governance and lifecycle
Alerts drift over time as systems and business rules change. Review scheduled query alerts quarterly. Track owners, last change time, and recent firing history. Decommission alerts that no longer drive action and consolidate overlapping checks.
Common pitfalls to avoid
- Unbounded queries that scan full tables at each run
- Ambiguous definitions, such as slow without a numeric threshold
- Mixed responsibilities where no one owns the response
- Sending raw data rather than the next step the operator should take
- Ignoring alerting system health and backlog
Putting it all together
Scheduled query alerts are a pragmatic, cross platform way to monitor what matters. Start with a tight, actionable definition. Choose an interval proportional to risk. Write efficient, incremental, read only queries. Deduplicate with a state store. Deliver concise messages through the right channel. Measure the jobs themselves and iterate.
If you prefer a mobile first workflow, an iOS app like Query Alert can connect to Postgres, MySQL, and MongoDB with encrypted, read only queries, schedule checks at custom intervals, and deliver push or email notifications with minimal setup.