Skip to main content

SQL Quality Rules

A type: sql rule runs a custom SQL query against the server and compares the single returned value to a threshold. It is the most flexible rule type — use it whenever a check can be expressed as a query. The query must be read-only, and is written in the dialect of the selected server.

Property-level example

schema:
- name: orders
properties:
- name: order_total
logicalType: integer
physicalType: bigint
required: true
quality:
- type: sql
description: 95% of all order total values are expected to be between 10 and 499 EUR.
query: |
SELECT quantile_cont(order_total, 0.95) AS percentile_95
FROM orders
mustBeBetween:
- 1000
- 99900

Schema-level example

schema:
- name: orders
quality:
- type: sql
description: The maximum duration between two orders should be less than 3600 seconds.
query: |
SELECT MAX(duration) AS max_duration
FROM (
SELECT EXTRACT(EPOCH FROM (order_timestamp - LAG(order_timestamp)
OVER (ORDER BY order_timestamp))) AS duration
FROM orders
) subquery
mustBeLessThan: 3600

Placeholders

Instead of hard-coding names, the query can reference the schema, the property, and the location the server names:

PlaceholderReplaced with
${model} / ${table} / ${object}the name of the schema object the rule belongs to
${field} / ${column} / ${property}the name of the property the rule belongs to (property-level rules only)
${schema}the server's schema
${dataset}the server's dataset (BigQuery)
${project}the server's project (BigQuery)
${catalog}the server's catalog (Databricks, Trino)
${database}the server's database (Postgres, SQL Server, Snowflake)

The $ is optional: {schema} works the same as ${schema}. A placeholder the server has no value for falls back to the name of the schema object.

quality:
- type: sql
description: The orders table is not empty.
query: |
SELECT COUNT(*) FROM ${project}.${dataset}.${table}
mustBeGreaterThan: 0

Comparators

The query must return a single value, which is compared using exactly one of:

ComparatorPasses when the result…
mustBeequals the value
mustNotBedoes not equal the value
mustBeGreaterThan / mustBeGreaterOrEqualTois above a lower bound
mustBeLessThan / mustBeLessOrEqualTois below an upper bound
mustBeBetween / mustNotBeBetweenis inside / outside a [min, max] range

The query must be a query

A rule computes a single value from the data, so the query must be a single read-only statement — a SELECT, a WITH … SELECT, a UNION, or a parenthesized query. Anything else is reported as a failed check and never sent to the data source: DDL (CREATE, DROP, ALTER), DML (INSERT, UPDATE, DELETE), COPY, ATTACH, INSTALL/LOAD, SET, PRAGMA, CALL, and a second statement appended behind a legitimate one.

This applies to every data source, and to every way the CLI is run. A data contract is not always written by the person whose credentials execute it.

SQL dialect

There is no dialect field on a quality rule. The dialect is derived from the type of the server the rule runs against, so a query is read the same way the data source will read it — BigQuery's backtick-quoted table names, Snowflake's SAMPLE, SQL Server's TOP and Postgres' ->> are all understood without any declaration:

Server typeSQL dialect
local, s3, gcs, azure, kafka, api, duckdbduckdb
postgrespostgres
redshiftredshift
mysqlmysql
oracleoracle
sqlservertsql
snowflakesnowflake
bigquerybigquery
databricksdatabricks
athenaathena
trinotrino
impalahive
dataframespark

The ODCS synonyms resolve to the spelling above before the dialect is looked up, so postgresql is read as postgres. A server declared as type: custom with customType: mssql is read as tsql, like sqlserver.

Files, Kafka topics and API responses are read through DuckDB, so a rule on those server types is written in DuckDB SQL — including its functions, such as read_parquet or list_contains.

If a rule fails with "could not be read as one", the query is not valid for the server type the contract declares; the message names the dialect it was read as.

Notes

  • Referencing the data — reference the schema/table by its name in the FROM clause (e.g. FROM orders).
  • severity — set severity: warning to report a failing rule without failing the run (see --fail-on).