Data Quality Metrics: The Ones Worth Tracking, With Formulas
July 2026 · Dataobservability
Alerted #data-eng 0.8s ago.
Downstream impact · consumers at risk
Live console · pick a break, watch it get caught
Data quality metrics are the numbers you compute against a dataset to decide whether it is fit to use: null rate (completeness), freshness lag (timeliness), row count delta (volume), duplicate rate (uniqueness), format and range conformance (validity), cross-system agreement (consistency), and schema change rate. Around those sit the operational metrics that describe how well your team responds: time to detection, time to resolution, incident count, and the share of incidents a monitor caught before a stakeholder did. The first group tells you whether the data is broken. The second tells you whether you would know.
This is the practical companion to our data quality framework guide, which covers dimensions, tiers, and ownership. This one covers which metrics to compute, the SQL that computes them, the trap in each, and how to report them without producing a number nobody can act on. Last updated July 2026.
What are data quality metrics?
Data quality metrics are measurable, repeatable numbers that quantify a specific property of a dataset, such as the percentage of rows with a missing customer ID or the minutes since a table last updated. A metric is only a metric if it has a formula, a check frequency, and a threshold. Without those three it is an opinion with a percent sign attached.
How do you measure data quality?
You measure data quality by running a small set of SQL checks on a schedule, per table, and comparing each result against a threshold or against that table's own history. Most teams need about eight table-level metrics and five operational ones. All are computable from a read-only warehouse connection, which is why these metrics won: they are cheap and not up for debate.
Completeness (null rate)
null_rate = count_if(col is null) / count(*)
SELECT COUNT_IF(customer_id IS NULL) / NULLIF(COUNT(*), 0) FROM orders;
The trap: empty strings, the literal text 'NULL', -1, and 1970-01-01 are not null, so a naive check passes while the column is functionally empty. Measure a missing rate that includes your sentinel values, and track it per column. A table-level average lets a clean column hide a broken one.
Freshness (data latency)
freshness_lag = now() - max(updated_at)
The trap: max(updated_at) describes the newest row, not the load. A pipeline that stamps the current timestamp on every row it touches looks fresh while delivering nothing new. Measure freshness against warehouse metadata (last DML time) as well as against the data itself. When the two disagree, that is your incident.
Volume (row count delta)
volume_delta = (rows_today - rows_expected) / rows_expected, where rows_expected is the median of the same weekday over the last 4 to 6 weeks.
The trap: a fixed threshold ("alert under 10,000 rows") is wrong on Black Friday and wrong on Christmas Day. Volume is seasonal on a weekly cycle for nearly every business, so compare like weekday to like weekday. Static row-count thresholds are the biggest source of alerts people learn to ignore.
Uniqueness (duplicate rate)
duplicate_rate = (count(*) - count(distinct key)) / count(*)
The trap: your key is usually composite. order_id may legitimately repeat across a slowly changing dimension, so a duplicate rate on it looks alarming and means nothing. State the grain in words first ("one row per order per status change"), then check against it.
Validity (format and range conformance)
validity_rate = count_if(value matches rule) / count(*)
SELECT COUNT_IF(status IN ('completed','shipped','delivered')) / NULLIF(COUNT(*), 0) FROM orders;
The trap: validity passes happily on data that is perfectly formatted and completely wrong. An amount_cents column full of dollar values is 100 percent valid and 100 times too small. Validity checks structure, never meaning.
Consistency across systems
consistency_gap = abs(warehouse_metric - source_metric) / source_metric
Run it as a daily reconciliation: order count in the app database against the warehouse, revenue in the billing system against fct_revenue. The trap is timing. If the two systems close their day in different time zones you will chase a gap that is pure boundary noise. Fix the window first.
Accuracy (and why it cannot be automated)
Accuracy is agreement with the real world, and nothing inside your warehouse knows what the real world is. Automating a proxy (reconciliation against a system you trust) only moves the question one hop. Real accuracy needs a human comparing a sample to ground truth: a finance close, an inventory count, an audit of 50 rows. Do it quarterly on tier-one tables. Any vendor selling an automated "accuracy score" is selling consistency with a nicer label.
Schema change rate
schema_changes_per_month = count(added + dropped + retyped columns)
Not a pass or fail metric, a risk indicator. A table with four schema changes a month is one whose producer does not know you exist, and it is where your next outage comes from. Track it per source system and take the worst offender to its owner.
Timeliness against SLA
sla_hit_rate = days_landed_before_deadline / total_days
Freshness is a gauge. Timeliness is a track record, and it is the one the business cares about: the question in the room is "will the dashboard be right at 9am", not "what is the lag in minutes".
| Metric | What it measures | Formula | Starting target (tier one) |
|---|---|---|---|
| Completeness | Missing required values | count_if(null or sentinel) / count(*) | Under 0.1 percent on keys |
| Freshness | Staleness right now | now() - max(updated_at) | Within 2x the load interval |
| Volume | Expected rows arrived | (rows_today - median_same_weekday) / median_same_weekday | Plus or minus 20 to 30 percent |
| Uniqueness | Duplicates on the grain | (count(*) - count(distinct key)) / count(*) | 0 percent on the primary key |
| Validity | Format, enum, range | count_if(matches rule) / count(*) | Over 99.5 percent |
| Consistency | Agreement with source | abs(a - b) / b | Under 0.5 percent daily gap |
| Schema change rate | Structural churn | Adds, drops, retypes per month | Zero unannounced changes |
| Timeliness | SLA track record | days_on_time / total_days | 99 percent monthly |
Those are starting targets we would set with our own team on day one, not industry benchmarks. Nobody has a credible benchmark for your null rate.
What data quality KPIs should a data team report?
Report the operational ones. Executives do not act on a null rate. They act on "we had 11 incidents last month, caught 8 before you did, and fixed them in a median of 90 minutes." Four numbers carry the signal:
- Incident count: distinct failures worth recording. Group related alerts into one incident or it is just noise.
- Time to detection: minutes from the data breaking to anyone knowing. The number an observability tool actually moves.
- Time to resolution: minutes from detection to fixed data, not to a Slack reply. Report median and 90th percentile, because the mean is a lie told by one bad weekend.
- Percent found by a monitor, not a human: the most honest number on the page. Under 50 percent means your stakeholders are your monitoring system.
Add monitor coverage, the share of tier-one tables with freshness, volume, and schema checks. Then read the next section before celebrating it.
Why coverage alone is a vanity metric
"98 percent of our tables are monitored" says nothing about whether the monitors work. A monitor that fires every Tuesday and gets dismissed every Tuesday still counts toward coverage while draining trust. Coverage means something only next to a true positive rate: true_positive_rate = alerts_actioned / alerts_fired. If that drops below roughly half, stop adding monitors and tune the ones you have, because you are training people to ignore you.
The same logic kills the blended "data quality score", the single 0 to 100 number some tools put on a homepage. It averages things that fail for unrelated reasons, so when it slides from 94 to 91 nobody can name one action to take. A number nobody can act on is decoration. Keep the components separate, give each an owner. Routing is covered in our guide to how to monitor data quality.
What is a good data quality score?
There is no industry number, and anyone quoting one is guessing. A good target is one you derive per table from the cost of being wrong. Your revenue fact table needs 100 percent uniqueness and 99 percent SLA adherence because a miss costs a board meeting. A marketing attribution staging table can sit at 95 percent and nobody notices.
Setting targets without making them arbitrary
Measure for 30 days before setting any threshold, so you know what normal looks like on your data instead of what you assume. Set the first threshold just outside observed normal (the worst value you saw that month, plus a margin) so it fires only on genuine departures. Tighten it only when a real incident slipped under the bar. Targets that tighten in response to misses are earned. Targets copied from a blog post are theater.
Building a data quality metrics dashboard
The dashboard has two halves serving two audiences. Do not merge them.
The engineering view is per table and continuous: freshness lag, rows against baseline, null rate by column, failing tests, open incidents with owners. Sort by tier, because nobody scrolls.
The executive scorecard is monthly, one page, trends only: incident count, median time to detection, median time to resolution, percent caught by monitors, and SLA adherence for tier-one tables by name. Show six months of history. Direction beats absolute value with a non-technical audience.
| Metric | How to report it | Cadence | Who owns it |
|---|---|---|---|
| Freshness and volume, tier one | Live status board, red or green | Continuous, alerts to Slack or PagerDuty | Table owner (a named person) |
| Null rate and validity by column | Column-level table, tier one only | Weekly | Analytics engineer for the domain |
| Incident count | Bar chart, six month trend | Monthly | Data platform lead |
| Time to detection and resolution | Trend line, median plus p90 | Monthly | Data platform lead |
| Percent caught by a monitor | Single number, trended | Monthly, in the leadership deck | Data platform lead |
| SLA adherence | Per table, percent of days on time | Monthly, per consuming team | Producing team |
| True positive rate on alerts | Internal only, drives tuning | Monthly | Whoever owns the monitors |
One practical note on the executive half: when a metric moves, someone wants to check the underlying numbers, and that request sits in an analyst's queue for two days. Plenty of teams now let stakeholders ask the warehouse questions in plain English instead, so the person who cares most about the number is the one who spots it looking wrong.
Which metrics should you automate first?
Freshness, volume, and schema, across every table, before you hand-write a single test. Those three cover most real incidents (a load that did not run, a load that delivered half the rows, a column that changed shape) and need no configuration, because a baseline learns them. Add null rate and distribution monitoring on tier-one tables next, which is where anomaly detection earns its keep by catching drift nobody thought to write a rule for. Hand-written tests come last, only for logic a baseline cannot infer: a revenue reconciliation, a regulatory range.
Use column-level lineage to decide which tables are tier one, since a count of downstream models and dashboards ranks them better than intuition. If you are still choosing a platform, the category breakdown is in our comparison of data quality tools, and the honest cost picture is in our rundown of the best data observability tools.
Start with the numbers you can compute this week
Take your ten most important tables. Compute null rate, freshness lag, volume delta, and duplicate rate on each. Write down what normal looks like for 30 days. Log every incident with a detection time, a resolution time, and who found it: a monitor, or a person. That is a real data quality program, and it fits in a spreadsheet.
If you would rather not build the collection layer yourself, that is what we do. Connect a read-only warehouse user and dataobservability.ai generates freshness, volume, schema, distribution, and lineage monitors in about 15 minutes, then routes alerts to Slack or PagerDuty with the blast radius attached. It is dbt-native, priced from $99 a month, and the 14-day free trial is enough to see your own numbers first. Either way, these are the metrics worth tracking, whoever computes them. The continuous side of the job is data quality monitoring.
Catch broken data before your stakeholders do
Connect your warehouse and get all five pillars monitoring in 15 minutes. Transparent pricing, no credit card.