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
| Function | Description | SQL Signature |
|---|---|---|
anofox_tab_volume | Row count within bounds | (table_name, min_rows, max_rows) -> TABLE |
anofox_tab_null_rate | NULL fraction below threshold | (table_name, column, max_null_rate) -> TABLE |
anofox_tab_distinct_count | Distinct values within bounds | (table_name, column, min, max) -> TABLE |
anofox_tab_schema_check | Required columns exist | (table_name, required_columns) -> TABLE |
anofox_tab_freshness | Latest 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
table_name | VARCHAR | Yes | - | Table or view to check |
min_rows | BIGINT | Yes | - | Minimum acceptable row count (NULL = none) |
max_rows | BIGINT | Yes | - | Maximum acceptable row count (NULL = none) |
Output
| Column | Type | Description |
|---|---|---|
status | VARCHAR | pass or fail |
row_count | BIGINT | Measured row count |
min_threshold, max_threshold | BIGINT | Echoed bounds |
message | VARCHAR | Human-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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
table_name | VARCHAR | Yes | - | Table or view to check |
column_name | VARCHAR | Yes | - | Column to inspect |
max_null_rate | DOUBLE | Yes | - | 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
table_name | VARCHAR | Yes | - | Table or view to check |
timestamp_column | VARCHAR | Yes | - | Timestamp column |
max_age | INTERVAL | Yes | - | Maximum acceptable age of the latest row |
reference_time | TIMESTAMP | No | now() | 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.