What you will be able to do
- Keep data quality rules in a central rules table or module and apply them to datasets by tag
- Build validation datasets that check row counts, completeness, primary key uniqueness, and statistical ranges
- Quarantine invalid records with a flag column or with separate SQL writes instead of dropping them
- Tell enforced Delta constraints (NOT NULL, CHECK) apart from informational ones (primary key, foreign key, unique)
1.Keeping rules in one place
A Lakeflow pipeline expectation is a named SQL Boolean constraint on a dataset, with an action of warn, drop, or fail. When many Silver tables need the same checks, writing each rule inline in every pipeline is hard to maintain. Databricks recommends storing expectation definitions separately from pipeline logic, grouping related rules with custom tags, and applying the same expectations consistently across similar datasets. Then you can update and audit a rule without touching pipeline source code.
Checkpoint 1 of 5· Check yourself
Many Silver tables need the same quality rules. Which approach does Databricks recommend?
Keeping definitions apart from pipeline code lets you apply them to many datasets and update or audit them without editing pipeline source.
“Store expectation definitions separately from pipeline logic.”Source: docs.databricks.com
The documented pattern keeps rules in a Delta table called rules, with columns name, constraint, and tag. A rule might be website_not_null with the constraint Website IS NOT NULL, tagged validity. A Python helper reads the rules that match a tag and returns them as a dict. That dict is exactly the input an expect_all decorator needs.
def get_rules(tag):
"""
loads data quality rules from a table
:param tag: tag to match
:return: dictionary of rules that matched the tag
"""
df = spark.read.table("rules").filter(col("tag") == tag).collect()
return {
row['name']: row['constraint']
for row in df
}
@dp.table
@dp.expect_all_or_drop(get_rules('validity'))
def raw_farmers_market():
return (
spark.read.format('csv').option("header", "true")
.load('/databricks-datasets/data.gov/farmers_markets_geographic_data/data-001/')
)You can also keep the same rules as a list of dicts in a Python module, rules_module.py, stored in the same folder as the pipeline notebook. Either way, records that fail any rule tagged validity are dropped from raw_farmers_market, and a second table applies the rules tagged maintained.
No. Dynamically loading expectations from a file is not supported in SQL. This pattern relies on Python building a dict and passing it to an expect_all decorator.
2.Validation tables: checks that span datasets
An expectation tests one record at a time, so it cannot directly answer questions such as "did we lose rows?" or "is this key unique?". The solution is a validation table: a separate dataset whose rows describe a property of other tables, with an expect_or_fail on those rows. In SQL, the fail action is written ON VIOLATION FAIL UPDATE. A bare EXPECT is the default warn action: it keeps the rows and only records metrics. The SQL examples below are shown as the documentation lists them, with a bare EXPECT, so as written they only warn. Add ON VIOLATION FAIL UPDATE to make a violation fail the update. To check that no data is lost during a transformation, compare row counts:
CREATE OR REFRESH MATERIALIZED VIEW count_verification(
CONSTRAINT no_rows_dropped EXPECT (a_count == b_count)
) AS SELECT * FROM
(SELECT COUNT(*) AS a_count FROM table_a),
(SELECT COUNT(*) AS b_count FROM table_b)The same validation-table idea covers other cross-dataset checks. For completeness, left-outer-join a validation copy to the report and expect r_key IS NOT NULL. For primary key uniqueness, group by the key and expect each group's count to be 1. Outliers use a different action. The documented Python pattern cross-joins new data with bounds computed from the last 30 days of history (average ± 3 standard deviations) and applies expect_or_drop on amount BETWEEN lower_bound AND upper_bound, so out-of-range rows are dropped rather than failing the update.
CREATE OR REFRESH MATERIALIZED VIEW report_compare_tests(
CONSTRAINT no_missing_records EXPECT (r_key IS NOT NULL)
)
AS SELECT v.*, r.key as r_key FROM validation_copy v
LEFT OUTER JOIN report r ON v.key = r.keyCREATE OR REFRESH MATERIALIZED VIEW report_pk_tests(
CONSTRAINT unique_pk EXPECT (num_entries = 1)
)
AS SELECT pk, count(*) as num_entries
FROM report
GROUP BY pkCREATE OR REFRESH MATERIALIZED VIEW validated_amounts (
CONSTRAINT within_statistical_range EXPECT (amount BETWEEN lower_bound AND upper_bound)
)
AS SELECT * FROM stats_validation_view;Databricks puts it plainly: expectations enforce data quality, not orchestration. To keep Gold from updating after a failed validation, put the validation logic and the downstream work in separate pipelines and orchestrate them with a job, making the downstream pipeline's task depend on the validation pipeline's task. A pipeline task fails when its update fails, so the downstream task runs only if validation succeeds.
Checkpoint 2 of 5· Check yourself
Gold aggregates must never be refreshed when a Silver row-count validation fails. What is the documented approach?
Gating needs control flow between pipeline tasks. Inside one pipeline, a failed validation does not stop downstream tables, and in triggered mode other flows keep running.
“split the validation logic and the downstream work into separate pipelines and orchestrate them with a job”Source: docs.databricks.com
3.Quarantining instead of dropping
Dropping a bad record throws it away, but sometimes Silver needs to keep invalid records so someone can review and repair them. In a pipeline, the quarantine pattern applies the rules with expect_all, which warns so metrics are still collected, and adds an is_quarantined column computed as NOT(rule1 AND rule2). The rows go into a temporary table partitioned by that flag, and two views split it into valid_trips_data and invalid_trips_data. Valid and invalid records then follow separate downstream paths.
You can do the same thing outside a pipeline with plain SQL. Use WHERE filters to send rows to a Silver table or a quarantine table:
INSERT INTO silver_table
SELECT * FROM bronze_table
WHERE event_timestamp <= current_time AND quantity >= 0;
INSERT INTO quarantine_table
SELECT * FROM bronze_table
WHERE event_timestamp > current_time OR quantity < 0;There are two writes here, and that is intentional. Databricks recommends always processing filtered data as a separate write operation, especially with Structured Streaming, and warns that using .foreachBatch to write to multiple tables can lead to inconsistent results. Some bad values are predictable and can be repaired rather than quarantined. If an upstream system writes -1 for a missing weight, a CASE WHEN weight = -1 THEN NULL ELSE weight END transformation fixes it once in Silver, so downstream queries don't each need special logic.
The expect_or_drop action removes records that violate its constraint from the target dataset. So a table whose constraint is the inverted condition, such as the NOT(rule1 AND rule2) expression used for is_quarantined, and that uses expect_or_drop, would keep only the rows the main table rejects. Compare that with the flag-column and WHERE-filter patterns above when you read the exam question below.
Checkpoint 3 of 5· Exam question
A compliance team wants every rejected row from a silver ingestion pipeline preserved for audit review, rather than simply discarded, while the clean rows still land in the main silver table. Which approach best satisfies this requirement using Lakeflow pipeline expectations?
Correct answer: A — Define a second table reading the same source with `expect_or_drop` on the inverted condition, so failing rows land in a quarantine dataset while the primary table drops them.
- A. Building a second dataset from the same source with the inverted condition and `expect_or_drop` produces exactly the two outputs the compliance team needs: a clean silver table and a separate quarantine table capturing rejected rows for audit, since expectations only support drop, warn, or fail on a single dataset.
- B. Failing the whole update on any invalid row blocks all new data, including the valid rows, and does not produce a separate auditable record of what was rejected; it also requires manual, out-of-pipeline reprocessing rather than an automated audit trail.
- C. Leaving invalid rows mixed into the silver table with only a warning does not separate rejected rows for audit purposes, and pushing the filtering to Gold means the silver table itself remains unreliable in the meantime.
- D. The `_rescued_data` column captures data that does not match the expected schema during ingestion, which is a schema-evolution feature unrelated to expectation-based row validation and does not create an audit trail of rows failing a business rule.
4.Delta table constraints: enforced versus informational
Expectations belong to pipelines. Delta tables can also enforce rules themselves when data is written, whichever process writes it. All constraints on Databricks require Delta Lake. There are two kinds, and they behave very differently: enforced constraints reject bad data, while informational constraints only describe relationships between tables.
| Constraint | Kind | On a violating write |
|---|---|---|
| NOT NULL | Enforced | Transaction fails with an error |
| CHECK (boolean expression) | Enforced | Transaction fails with an error |
| PRIMARY KEY / FOREIGN KEY / UNIQUE | Informational | Not enforced; may improve performance through query optimizations |
You add or drop NOT NULL with ALTER TABLE ... ALTER COLUMN, and you manage CHECK constraints with ALTER TABLE ADD CONSTRAINT and DROP CONSTRAINT. Before Databricks adds either kind to an existing table, it checks that every existing row already satisfies it. A CHECK expression must be deterministic, and it cannot use user-defined, aggregate, or window functions, or functions that return multiple rows. Adding a constraint also upgrades the table writer protocol if it is below version 3, which can affect external Delta Lake clients.
CHECK is also how you enforce value ranges and text patterns on write. A range rule is a CHECK such as column_name >= 0 AND column_name <= 100. A regex rule uses REGEXP or RLIKE inside a CHECK, as shown below. NOT NULL can be enabled on an existing table only if no existing record in the column is null.
CREATE TABLE table_name (
column_name STRING CHECK (column_name REGEXP '^[A-Za-z0-9]+$')
);Informational constraints have version limits too. Primary key and foreign key constraints are available for Unity Catalog and Delta Lake tables in Databricks Runtime 13.3 LTS and above, and GA in 15.2 and above. Unique constraints are in Public Preview, in Databricks SQL and Databricks Runtime 18.2 and above. Two other mechanisms support Silver and Gold quality. Delta schema enforcement ensures that data written to a table adheres to a predefined schema. Databricks data quality monitoring (data profiling) lets you watch the statistical properties and quality of your tables over time.
Checkpoint 4 of 5· Fill the gap
Complete the statement that adds an enforced date-range rule to a Delta table.
ALTER TABLE main.default.people_demo ADD CONSTRAINT dateWithinRange ? (birthDate > '1900-01-01');Delta tables use CHECK for enforced Boolean rules. EXPECT is the pipeline expectation keyword, and UNIQUE is an informational constraint.
Source: docs.databricks.comCheckpoint 5 of 5· Check yourself
A Gold dimension table declares CONSTRAINT pk PRIMARY KEY(customer_id). A job inserts a second row with an existing customer_id. What happens?
Primary key, foreign key, and unique constraints are not enforced. To actually check uniqueness, use a validation dataset such as report_pk_tests.
“Informational constraints (primary key, foreign key, and unique constraints) define relationships between fields in tables and aren't enforced.”Source: docs.databricks.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.If a validation table's expect_or_fail fails, any table that reads from it in the same pipeline will not update.Why is that wrong?
Reading a validation table does not make a dataset wait for the result. To gate downstream work, use separate pipelines coordinated by a job.
Covered in Validation tables: checks that span datasets
2.Declaring a PRIMARY KEY on a Delta table guarantees that the key is unique.Why is that wrong?
Primary key, foreign key, and unique constraints are informational only. NOT NULL and CHECK are the constraints that are enforced.
Covered in Delta table constraints: enforced versus informational
Practise it for real
Drive a pipeline table's drop expectations from a central, tagged rules table
1.Create the rules table with CREATE OR REPLACE TABLE rules AS SELECT col1 AS name, col2 AS constraint, col3 AS tag FROM (VALUES ...), including the website_not_null rule tagged validity.
Why: This keeps rule definitions separate from pipeline code, so you can change them without editing the pipeline.
You should see: A rules table with name, constraint, and tag columns.
2.In the pipeline's Python source, define get_rules(tag) so that it reads the rules table, filters on tag, and returns a {name: constraint} dict.
Why: The expect_all decorators require a dict of description and constraint pairs.
You should see: Calling get_rules('validity') returns a dict that contains website_not_null.
3.Decorate the raw_farmers_market table with @dp.expect_all_or_drop(get_rules('validity')) and run a pipeline update.
Why: Drop removes failing records and still records metrics for each rule.
You should see: Records with a null Website are missing from the table, and the dataset's Data quality tab shows the dropped count for website_not_null.
Stuck? Get a nudge
If the table ends up with no rows at all, check that the constraint strings in the rules table are valid SQL for the CSV's column names.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Store expectation definitions separately from pipeline logic.”
↩︎ Keeping rules in one place“Dynamically loading expectations from a file is not supported in SQL.”
↩︎ Keeping rules in one place“validates row count equality between table_a and table_b to check that no data is lost during transformations”
↩︎ Validation tables: checks that span datasets“A validation table doesn't gate its downstream tables.”
↩︎ Validation tables: checks that span datasets“enable separate processing paths for valid and invalid records in downstream operations”
↩︎ Quarantining instead of dropping“Expectations enforce data quality, not orchestration.”
↩︎ Exam trap 1“split the validation logic and the downstream work into separate pipelines and orchestrate them with a job”
↩︎ Checkpoint - 2.
“The expect_all decorators require descriptions and constraints to be passed as a dict of key-value pairs.”
↩︎ Keeping rules in one place - 3.https://docs.databricks.com/aws/en/ldp/expectationsOfficial docs
“fail | EXPECT ... ON VIOLATION FAIL UPDATE | dp.expect_or_fail”
↩︎ Validation tables: checks that span datasets“Records that violate the expectation are dropped from the target dataset”
↩︎ Quarantining instead of dropping - 4.
“Databricks recommends always processing filtered data as a separate write operation, especially when using Structured Streaming.”
↩︎ Quarantining instead of dropping“Using .foreachBatch to write to multiple tables can lead to inconsistent results.”
↩︎ Quarantining instead of dropping“Schema enforcement ensures that data written to a table adheres to a predefined schema.”
↩︎ Delta table constraints: enforced versus informational“monitor the statistical properties and quality of the data in all of the tables in your account”
↩︎ Delta table constraints: enforced versus informational“Enforced constraints include NOT NULL and CHECK constraints.”
↩︎ Exam trap 2 - 5.
“When a constraint is violated, the transaction fails with an error.”
↩︎ Delta table constraints: enforced versus informational“ALTER TABLE ADD CONSTRAINT verifies that all existing rows satisfy the constraint before adding the constraint to the table.”
↩︎ Delta table constraints: enforced versus informational“All constraints on Databricks require Delta Lake.”
↩︎ Delta table constraints: enforced versus informational“Primary key, foreign key, and unique constraints are informational only and aren't enforced.”
↩︎ Delta table constraints: enforced versus informational“Informational constraints (primary key, foreign key, and unique constraints) define relationships between fields in tables and aren't enforced.”
↩︎ Checkpoint