Blueprints Blog Contact About

Constraints

Validation is part of execution — not a separate step

Constraints stop bad data before it enters your tables.

If a single row violates a constraint, the entire model fails and nothing is written.

No separate testing framework. No post-hoc checks. Validation is part of execution.

Why This Matters

Most data tools validate data after it’s already written. By then, bad data is already in your tables.

OndatraSQL validates before insertion — so invalid data never lands.

Mental Model

Constraints are enforced on the result of your query before it is written.

If they fail:

  • Nothing is inserted
  • Downstream models do not run

Usage

Add constraints directly in your model:

-- @constraint: column_name PATTERN

You can define multiple constraints per model.

-- @constraint: email EMAIL
-- @constraint: total >= 0
-- @constraint: status IN ('pending', 'shipped', 'cancelled')
-- @constraint: customer_id REFERENCES customers(id)

If any of these fail, the model does not run.

Common Patterns

PatternExampleWhat it checks
PRIMARY KEYid PRIMARY KEYNOT NULL + UNIQUE
NOT NULLemail NOT NULLNo NULL values
UNIQUEcode UNIQUENo duplicates
IN (...)status IN ('active', 'inactive')Allowed values
>= Ntotal >= 0Minimum value
EMAILemail EMAILEmail format
REFERENCEScustomer_id REFERENCES customers(id)Foreign key
NULL_PERCENTemail NULL_PERCENT < 10Max NULL percentage

All 26 Patterns

Identity & Nullability

PatternExampleDescription
PRIMARY KEYid PRIMARY KEYNOT NULL + UNIQUE combined
NOT NULLemail NOT NULLColumn must not be NULL
UNIQUEcode UNIQUENo duplicate values
(col1, col2) UNIQUE(year, month) UNIQUEComposite uniqueness
NOT EMPTYname NOT EMPTYNot NULL and not empty string

Comparison

PatternExampleDescription
>= Nage >= 0Greater than or equal
<= Nscore <= 100Less than or equal
> Namount > 0Greater than
< Ndiscount < 1Less than
= Nversion = 1Equal to
!= Nstatus != 0Not equal to
BETWEEN x AND yrating BETWEEN 1 AND 5Range check

Membership

PatternExampleDescription
IN (...)status IN ('active', 'inactive')Allowed values (auto-quotes unquoted strings)
NOT IN (...)role NOT IN ('deleted')Forbidden values

Pattern Matching

PatternExampleDescription
LIKEemail LIKE '%@%'SQL LIKE pattern
NOT LIKEname NOT LIKE 'test%'Inverse LIKE
MATCHESphone MATCHES '^\+[0-9]+'Regular expression
EMAILemail EMAILEmail format validation
UUIDid UUIDUUID format validation

String Length

PatternExampleDescription
LENGTH BETWEEN a AND bname LENGTH BETWEEN 1 AND 255String length range
LENGTH = Ncode LENGTH = 3Exact string length

Referential & Custom

PatternExampleDescription
REFERENCEScustomer_id REFERENCES customers(id)Foreign key check
CHECKtotal CHECK (total >= 0)Custom SQL expression

Statistical

PatternExampleDescription
AT_LEAST_ONEemail AT_LEAST_ONEAt least one non-NULL value
NOT_CONSTANTstatus NOT_CONSTANTAt least 2 distinct values
NULL_PERCENTemail NULL_PERCENT < 10Maximum NULL percentage
DISTINCT_COUNT OP Nstatus DISTINCT_COUNT >= 3Number of distinct values (cardinality)
DUPLICATE_PERCENT < Nemail DUPLICATE_PERCENT < 5Maximum duplicate percentage
SEQUENTIALid SEQUENTIALNo gaps in integer sequence
SEQUENTIAL(N)id SEQUENTIAL(5)Gaps allowed only by step N
NO_OVERLAP(start_date, end_date) NO_OVERLAPTime intervals don’t overlap

Full Example

-- @kind: merge
-- @unique_key: order_id
-- @constraint: order_id PRIMARY KEY
-- @constraint: total >= 0
-- @constraint: status IN ('pending', 'shipped', 'delivered', 'cancelled')
-- @constraint: status DISTINCT_COUNT >= 2
-- @constraint: customer_id REFERENCES customers(id)
-- @constraint: email EMAIL
-- @constraint: email DUPLICATE_PERCENT < 10
-- @constraint: order_date NOT NULL

SELECT order_id, customer_id, email, total, status, order_date
FROM raw.orders

Compared to Traditional Validation

Traditional approach:

  • Write tests separately (dbt tests, Great Expectations)
  • Run after data is written
  • Failures require cleanup

OndatraSQL:

  • Define constraints in the model
  • Run before data is written
  • Fail fast — no bad data enters the table