Import File Example: Referential Integrity Rule

Prev Next

What it checks

One rule, applied to many columns: every value in a foreign key column must exist in its parent table. A row that fails this rule is an orphan, and orphans in a fact table mean revenue, claims, or transactions that no dimension can explain.

The rule's SQL lives on the template. The spreadsheet supplies what varies per column: which parent table and column to look up, and how many orphans are tolerated before the child test fails. That last part matters in practice. Late-arriving dimensions produce a known trickle of orphans in some feeds, and a rule with no allowance would fail every night for a reason nobody intends to fix.

Part of Template Test Examples: Import File.

The template

Setting Value
Name Import File Example - Referential Integrity Rule
Level Column
Generation source Import File
Severity High
Quality dimension Consistency
Child test name Orphans: {{table.name}}.{{column.name}} -> {{var.parent_table}}.{{var.parent_column}}
Child folder Relative, {{var.template_test_name}}
Test data set Script on the template's data source, single value, numeric (below)
Control data set Numeric value: {{var.max_orphan_rows}}
Value success criteria Custom calculation: testNumericValue <= controlNumericValue
Overall success Value, tolerance 0
Missing result actions Fail on both sides

The test data set SQL:

SELECT COUNT(*) AS ORPHAN_ROWS
FROM {{schema.name}}.{{table.name}} c
LEFT JOIN {{var.parent_schema}}.{{var.parent_table}} p
  ON c.{{column.name}} = p.{{var.parent_column}}
WHERE c.{{column.name}} IS NOT NULL
  AND p.{{var.parent_column}} IS NULL

Null foreign keys are excluded on purpose. A null is a different defect (a completeness problem) and belongs to a different test.

Variables declared on the template:

Reference key Type Purpose
parent_schema Schema Schema of the parent table
parent_table Table Parent table
parent_column Column Column in the parent that the child key must match
max_orphan_rows Numeric Orphan rows allowed before the child test fails. Default 0

Nothing on the template is ticked From File. The allowance is a variable referenced directly in the control's Target Value field, the same way the drift templates shipped with Validatar carry their lookback windows. One mechanism for every per-row value, and a declared default to fall back on.

Two metadata links are set on the test data set. {{schema.name}}.{{table.name}}.{{column.name}} carries coverage for the foreign key column. {{var.parent_schema}}.{{var.parent_table}}.{{var.parent_column}} records the relationship to the parent without counting as coverage for it.

The import file

import-file-example-referential-integrity.xlsx, one sheet, header row plus 6 data rows.

Header Meaning
schema.name, table.name, column.name The catalogued foreign key column. Column level, so all three are required
test.key Unique key, written as CHILD.COLUMN -> PARENT for readability
var.parent_schema, var.parent_table, var.parent_column Where the key must resolve
var.max_orphan_rows Allowed orphans

The rows:

Foreign key column Parent Allowed orphans
STAR_SCHEMA.FACT_SALES.CUSTOMER_KEY STAR_SCHEMA.DIM_CUSTOMER.CUSTOMER_KEY 0
STAR_SCHEMA.FACT_SALES.PRODUCT_KEY STAR_SCHEMA.DIM_PRODUCT.PRODUCT_KEY 0
STAR_SCHEMA.FACT_PAYMENT.POLICY_ID STAR_SCHEMA.DIM_POLICY.POLICY_ID 0
STAR_SCHEMA.FACT_TRANSACTION.CLAIM_ID STAR_SCHEMA.DIM_CLAIM.CLAIM_ID 6,000
STAR_SCHEMA.FACT_TRANSACTION.PAYMENT_ID STAR_SCHEMA.FACT_PAYMENT.PAYMENT_ID 0
STAR_SCHEMA.DIM_CLAIM.POLICY_ID STAR_SCHEMA.DIM_POLICY.POLICY_ID 0

The CLAIM_ID row shows the allowance in use. Claims arrive in FACT_TRANSACTION before DIM_CLAIM catches up, so a few thousand orphans are the accepted state and the threshold is set above it.

What materializes

Six child tests, one per foreign key column, in a folder named after the template. The PRODUCT_KEY child, for instance, gets this test script:

SELECT COUNT(*) AS ORPHAN_ROWS
FROM STAR_SCHEMA.FACT_SALES c
LEFT JOIN STAR_SCHEMA.DIM_PRODUCT p
  ON c.PRODUCT_KEY = p.PRODUCT_KEY
WHERE c.PRODUCT_KEY IS NOT NULL
  AND p.PRODUCT_KEY IS NULL

and a control value of 0. The CLAIM_ID child gets a control value of 6000.

Results on SAMPLE_DW

Verified 2026-09-11.

Child Orphans Allowed Result
FACT_SALES.CUSTOMER_KEY -> DIM_CUSTOMER 0 0 Pass
FACT_SALES.PRODUCT_KEY -> DIM_PRODUCT 254 0 Fail
FACT_PAYMENT.POLICY_ID -> DIM_POLICY 0 0 Pass
FACT_TRANSACTION.CLAIM_ID -> DIM_CLAIM 5,045 6,000 Pass
FACT_TRANSACTION.PAYMENT_ID -> FACT_PAYMENT 0 0 Pass
DIM_CLAIM.POLICY_ID -> DIM_POLICY 0 0 Pass

The 254 sales rows with an unknown product are a real defect in the sample warehouse. They also demonstrate the value of the allowance column: set that row's var.max_orphan_rows to 254 and the test passes while the defect is being fixed, then set it back to 0 afterwards.

Adapting it

Cover your own star schema. One row per foreign key. The child column must be in the catalog; the parent named in the var. columns only needs to be queryable.

Parent in another schema or database. var.parent_schema is free text inside the SQL, so OTHER_DB.OTHER_SCHEMA works on platforms that allow three-part names.

Composite keys. The template joins on one column. For a two-column key, copy the template and add var.child_column_2 and var.parent_column_2 to the join, then add those headers to the file.

Turn the allowance into a percentage. Change the test SQL to return 100.0 * COUNT(orphans) / COUNT(*) and treat var.max_orphan_rows as a percent. The comparison stays <=.

Pitfall: column level means three required headers. Leave out column.name and the upload fails with Missing column: "column.name".

Pitfall: case on the parent names. They are pasted into SQL as written. Snowflake folds unquoted identifiers to upper case, so lower case works there, but match your platform's rules.

Related