Skip to main content

Quality Metrics

AnoFox Tabular ships quality assertions as DuckDB table functions: each one scans a table, evaluates a threshold and returns exactly one summary row with a status of pass or fail, the measured value and a human-readable message. Because the result is a row — not a scalar — the checks compose with plain SQL: filter on status, UNION them into a report, or INSERT them into a history table.

Every function is registered under its canonical anofox_tab_* name and a short alias (volume, null_rate, ...). The examples below use the aliases.

Quick Reference

FunctionDescriptionSQL Signature
anofox_tab_volumeRow count within bounds(table_name, min_rows, max_rows) -> TABLE
anofox_tab_null_rateNULL fraction below threshold(table_name, column, max_null_rate) -> TABLE
anofox_tab_distinct_countDistinct values within bounds(table_name, column, min, max) -> TABLE
anofox_tab_schema_checkRequired columns exist(table_name, required_columns) -> TABLE
anofox_tab_freshnessLatest timestamp recent enough(table_name, ts_column, max_age [, ref_time]) -> TABLE

Statistical outlier assertions (zscore, iqr) live on the Anomaly page. For the full rule library — regex conformance, value sets, aggregates, duplicates, referential integrity, custom predicates, time-aware baselines and the config-driven suite runner run_checks — see Checks.


Core Assertions (5 functions)

anofox_tab_volume

Assert that a table's row count lies between min_rows and max_rows (NULL = unbounded).

Parameters

ParameterTypeRequiredDefaultDescription
table_nameVARCHARYes-Table or view to check
min_rowsBIGINTYes-Minimum acceptable row count (NULL = none)
max_rowsBIGINTYes-Maximum acceptable row count (NULL = none)

Output

ColumnTypeDescription
statusVARCHARpass or fail
row_countBIGINTMeasured row count
min_threshold, max_thresholdBIGINTEchoed bounds
messageVARCHARHuman-readable result

Example

SELECT * FROM volume('orders', 1000, 1000000);
-- status: pass, row_count: 52310, message: 'Row count 52310 is within acceptable range'

anofox_tab_null_rate

Assert that the fraction of NULL values in a column does not exceed max_null_rate.

Parameters

ParameterTypeRequiredDefaultDescription
table_nameVARCHARYes-Table or view to check
column_nameVARCHARYes-Column to inspect
max_null_rateDOUBLEYes-Maximum allowed NULL fraction (0.0–1.0)

Returns: one row with status, null_count, total_count, null_rate, threshold, message. An empty table passes trivially with an explicit message.

Example

SELECT * FROM null_rate('customers', 'email', 0.05);
-- status: fail, null_rate: 0.11, message: 'Null rate 0.11 (550/5000) exceeds maximum 0.05'

anofox_tab_distinct_count

Assert that the number of distinct values in a column lies between min_distinct and max_distinct (NULL = unbounded). Useful for both key columns (expect ~row count) and categorical columns (expect a small, known cardinality).

Example

-- a status column should have a handful of values, not hundreds
SELECT * FROM distinct_count('orders', 'status', 1, 10);

anofox_tab_schema_check

Assert that a table contains all required column names. Missing columns are listed in the result.

Example

SELECT * FROM schema_check('orders', ['order_id', 'customer_id', 'amount', 'created_at']);
-- status: fail, missing_columns: [created_at], message: 'Table orders is missing 1 column(s)'

anofox_tab_freshness

Assert that the most recent value in a timestamp column is younger than max_age. An optional reference_time makes the check deterministic (useful in tests and backfills); it defaults to now().

Parameters

ParameterTypeRequiredDefaultDescription
table_nameVARCHARYes-Table or view to check
timestamp_columnVARCHARYes-Timestamp column
max_ageINTERVALYes-Maximum acceptable age of the latest row
reference_timeTIMESTAMPNonow()Point in time to measure against

Example

SELECT * FROM freshness('events', 'created_at', INTERVAL '1 hour');
-- status: fail, age_seconds: 9640, message: 'Data is stale. Latest update: ... (age: 9640s, max allowed: 3600s)'

Composing a quality report

Because every assertion returns the same kind of row, a data-health report is a UNION ALL:

SELECT 'volume' AS check, status, message FROM volume('orders', 1000, NULL)
UNION ALL
SELECT 'email nulls', status, message FROM null_rate('customers', 'email', 0.05)
UNION ALL
SELECT 'fresh', status, message FROM freshness('events', 'created_at', INTERVAL '1 day');

For anything beyond a handful of checks, don't hand-write the UNION — define the checks once in a table and let run_checks expand it for you, complete with warn-only severities, per-partition results and persistence.


🍪 Cookie Settings