FFastero

Connect any database. Ask in plain English.

Try free
Back to blog

Blog article

How to Set Up Data Quality Alerts Across Databases (Without Monte Carlo)

Data quality issues are silent until someone notices a wrong number in a dashboard. Here's how to set up SQL-based quality alerts — row counts, NULL spikes, freshness, schema drift, distribution shifts — across Postgres, MySQL, and your warehouse, using scheduled checks and threshold triggers.

Fastero Dev TeamFastero Dev Team
2026-08-06
data-qualitysqlalertsmonitoringtriggersobservability
How to Set Up Data Quality Alerts Across Databases (Without Monte Carlo)

Data quality problems don't announce themselves. A broken ETL job doesn't send you an email saying "I stopped loading rows." A schema change upstream doesn't pop up a warning before your dashboard starts returning wrong numbers. You find out when a VP asks why the revenue chart looks off, or when a model starts producing garbage predictions because the training data shifted under it. By then, the bad data has already flowed downstream into reports, ML features, and decisions that can't be un-made.

The fix is proactive checks: SQL queries that run on a schedule, test a specific quality dimension, and fire an alert when something crosses a threshold. Five check types catch 90% of real incidents. Here's the SQL for each one.

What are the five types of data quality checks?

Every data quality incident fits into one of five categories. You don't need all five on every table — start with freshness and row counts on your most critical tables, then expand.

Freshness staleness — a table that should update hourly hasn't updated in 12 hours. The most common failure mode, the easiest to catch.

Row count anomalies — a daily load that normally brings in 50,000 rows suddenly brings in 200, or zero. Almost always a pipeline problem, not a real business event.

NULL rate spikes — a required field starts coming back null at a rate it never used to. Usually an upstream contract break: a form validation removed, an API field that went optional, a join that stopped matching.

Schema drift — a column renamed, dropped, or changed type. Downstream queries either break loudly or, worse, silently return wrong results because a type coercion papered over the change.

Distribution shifts — the shape of the data changed even though volume and schema look fine. Average order value halved overnight, a status column gained three values nobody expected, a numeric field's range moved by an order of magnitude.

How do you write SQL for each check type?

Each check is a single query that returns a number you can compare against a threshold. Here's working SQL for all five, written against tables you'd actually have.

Freshness

SELECT
  EXTRACT(EPOCH FROM (NOW() - MAX(updated_at))) / 3600
    AS hours_since_update
FROM orders;
-- Alert when hours_since_update > 4

Set the threshold with headroom. If orders updates every hour, don't alert at 61 minutes — alert at 4 hours, so you're catching real staleness, not routine latency jitter.

Row count anomaly

SELECT COUNT(*) AS rows_loaded
FROM events
WHERE created_at >= NOW() - INTERVAL '1 day';
-- Alert when rows_loaded < 5000

Pick the floor from a couple weeks of observation. A table that normally ingests 40,000-60,000 rows/day? Set the floor at 10,000, not 39,999. You're looking for "something broke," not "slightly below average."

NULL rate spike

SELECT
  COUNT(*) FILTER (WHERE customer_email IS NULL) * 100.0
    / NULLIF(COUNT(*), 0) AS null_pct
FROM users
WHERE created_at >= NOW() - INTERVAL '1 day';
-- Alert when null_pct > 2

For a required field like email, anything above 1-2% null is almost certainly a bug. For an optional field like company_name, you'll need a higher tolerance — profile your baseline first.

Schema drift

SELECT column_name, data_type, ordinal_position
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'orders'
ORDER BY ordinal_position;

This one is different — you can't threshold a single number. Snapshot the result daily, diff it against the previous run, and alert on any change. Fastero's schema drift detection does this automatically. DIY, you'll need to store the previous snapshot somewhere and compare.

Distribution shift

WITH today AS (
  SELECT AVG(order_total) AS avg_today
  FROM orders
  WHERE created_at >= NOW() - INTERVAL '1 day'
),
baseline AS (
  SELECT AVG(order_total) AS avg_baseline
  FROM orders
  WHERE created_at BETWEEN NOW() - INTERVAL '8 days'
                       AND NOW() - INTERVAL '1 day'
)
SELECT
  t.avg_today,
  b.avg_baseline,
  ABS(t.avg_today - b.avg_baseline)
    / NULLIF(b.avg_baseline, 0) * 100 AS pct_drift
FROM today t, baseline b;
-- Alert when pct_drift > 25

Twenty-five percent is a starting guess. Widen it for genuinely volatile metrics (ad spend, event counts). Tighten it for stable ones (subscription price, tax rate).

Fastero

Connect your database. Ask questions. Get dashboards.

Postgres, BigQuery, Snowflake, and 10+ sources — live-connected, AI-powered, no dashboard builder learning curve.

Try free →

Where do quality checks fit in a multi-database pipeline?

When you're pulling from multiple sources into a warehouse or DuckDB store, the question isn't just "what to check" but "where in the pipeline to check it."

  Source databases              Quality gates             Consumers
┌──────────┐
│ Postgres │──→ ① Freshness ──→ ┌──────────┐
│ (orders) │    Row counts      │          │
└──────────┘                    │  DuckDB  │    ③ Final
┌──────────┐                    │  Store / │──→ row counts ──→ Dashboards
│  MySQL   │──→ ① NULL rates    │Warehouse │    distributions   ML models
│ (users)  │    Schema drift    │          │                    Reports
└──────────┘                    └──────────┘
┌──────────┐                        ↑
│ Shopify  │──→ ① Freshness         │
│  (API)   │    Row counts      ② Schema
└──────────┘                      checks
                                 after sync

Gate 1 — at the source. Run freshness and NULL checks against each source database before or immediately after extraction. Catch problems before bad data enters the pipeline.

Gate 2 — after sync. Run schema checks against the destination after a sync completes. Did the sync drop a column? Did a type change survive the translation?

Gate 3 — before consumption. Run distribution and row count checks against the final tables that dashboards and models query. This is your last line of defense before a wrong number appears in someone's report.

You don't need all three gates on day one. Start with gate 1 (freshness on your two most critical source tables) and expand from there.

How do you schedule and alert on these checks?

The SQL is the easy part. The harder problem is making these run reliably, on a schedule, with routing to the right channel when they fail. Four approaches, in order of operational overhead.

Cron + Python scripts

Write a script per check, connect to the database, run the query, compare the result, post to a Slack webhook. Works for 2-3 checks on a single database. Falls apart when you have checks across three databases — now you're managing connection strings, credential rotation, and silent failures across a fleet of scripts.

dbt tests

If you already run dbt, express your checks as singular tests or custom generic tests. The results flow through your existing CI/alerting. The limitation: dbt tests run when dbt runs, not on an independent schedule, and they only cover tables inside your dbt project. Source tables outside dbt need a different mechanism.

Great Expectations

Python-native, highly configurable, good if your team already thinks in Python and you want version-controlled expectation suites. The overhead is real, though — you're maintaining a Python project alongside your data project, with its own dependency management, deployment, and execution environment.

Fastero triggers

Connect each database once. Write the SQL query. Set the threshold condition. Pick the schedule (every 15 minutes, hourly, daily). Choose the alert channel — Slack, email, or webhook. Three minutes per check, no scripts to deploy, no cron box to maintain. Fastero runs the query against your connected database on the schedule you set and only fires the alert when the condition is met. Full execution history is visible in the UI — when it ran, what the query returned, whether the alert fired.

Here's the setup for a NULL rate alert:

  1. Connect your Postgres (or MySQL, BigQuery, Snowflake — 30+ connectors)
  2. Write the query — the NULL rate SQL from above
  3. Set the conditionnull_pct > 2
  4. Route the alert#data-quality in Slack
  5. Schedule — every 6 hours

Repeat for each check. Because Fastero already holds connections to all your databases, you can run checks across Postgres, MySQL, and BigQuery from one place without managing separate credential stores.

How do the tools compare?

Great Expectations dbt tests Monte Carlo Fastero
Language Python SQL (Jinja) No-code SQL
Check types All five Row count, NULL, schema, custom All five + ML anomaly All five
Multi-database Config per source One warehouse Auto-discover Connect each source
Scheduling You manage (Airflow, cron) Tied to dbt runs Continuous Built-in cron
Alerting You build (webhook, email) Elementary / webhook Built-in + PagerDuty Slack, email, webhook
ML anomaly detection No No Yes No (threshold-based)
Lineage No Partial (dbt DAG) Full automated Column-level
Cost Free (OSS) Free (OSS) / dbt Cloud $30-50k/year Free tier available
Setup time Hours-days Minutes per test Days (onboarding) Minutes per check

Monte Carlo wins on automation — point it at your warehouse and it monitors everything without writing a single check. But if you have 10-50 critical tables across a few databases and a team that can write SQL, threshold-based checks catch the same incidents for a fraction of the cost.

What's the fastest way to get started?

Don't try to boil the ocean. Pick your two most critical tables — probably orders and users — and set up freshness, row count, and NULL rate checks on each. That's six checks total. In Fastero, about 20 minutes. With cron scripts, maybe two hours. Either way, you'll catch the majority of real incidents, and you can add distribution and schema checks later.

For more depth: data quality monitoring without Monte Carlo covers the full DIY approach, schema drift detection and column profiling goes deeper on structural checks, and SQL alerts without Datadog covers routing and escalation. See also the best data observability tools comparison.

FAQ

Do I need a data warehouse to run quality checks across databases? No. You can run checks directly against each source database — Postgres, MySQL, BigQuery, whatever. A warehouse helps if you want to check data after it's been combined, but source-level checks (freshness, NULL rates) should run against the sources themselves, not after extraction.

How often should data quality checks run? Match the check frequency to the table's update frequency with headroom. A table that updates every hour? Check every 2-4 hours. A daily batch load? Check once a day, an hour after the expected load time. Running checks too frequently just generates noise without catching anything faster.

Can I use Great Expectations and Fastero together? Yes. Great Expectations handles complex, code-defined validation suites well — distribution tests, multi-column constraints, custom Python checks. Fastero handles the scheduling and alerting layer for simpler SQL-based checks. Some teams run GE as part of their pipeline and Fastero for real-time threshold monitoring on source tables.

What's the difference between data quality alerts and data observability? Data quality alerts are checks you define — "alert me when NULL rate exceeds 2%." Data observability platforms like Monte Carlo learn what "normal" looks like and alert on deviations automatically, without you setting thresholds. The tradeoff is cost and complexity versus manual threshold management. Most teams under 20 people don't need the automated approach yet.

How do I avoid alert fatigue from too many quality checks? Start with loose thresholds on critical tables only. Tighten thresholds after two weeks of observation once you know your real variance. Route different severity levels to different channels — critical checks to a paged channel, informational checks to a weekly digest. Five well-tuned alerts beat fifty noisy ones.


Try Fastero free — connect your databases, write SQL quality checks, and get Slack alerts when something breaks. No credit card required.

Ready to try it yourself?

Connect your database, ask questions in plain English, and get live dashboards — in under 2 minutes. No credit card required.