What each expectation action does to a bad record, and how quarantine is built.
From Ultra Transcenders DBX-DEP by Tony Rough (coming December 2026)
Expectations are named SQL Boolean checks applied to each record as it passes through a query. There are three built-in actions; quarantine is a pattern built from them, not a fourth action.
| Action | SQL | Python | Result | Metrics |
|---|---|---|---|---|
| warn (default) | EXPECT |
dp.expect |
Invalid rows are written to the target | Yes |
| drop | EXPECT ... ON VIOLATION DROP ROW |
dp.expect_or_drop |
Invalid rows are removed before the write; the count is logged | Yes |
| fail | EXPECT ... ON VIOLATION FAIL UPDATE |
dp.expect_or_fail |
The update fails and rolls back; manual intervention needed | No, since the update fails |
CREATE OR REFRESH STREAMING TABLE customers(
CONSTRAINT valid_age EXPECT (age BETWEEN 0 AND 120),
CONSTRAINT valid_id EXPECT (id IS NOT NULL)
ON VIOLATION DROP ROW
) AS SELECT * FROM STREAM(raw_customers);The constraint must be valid SQL and cannot include custom Python functions, external service calls or subqueries that reference other tables. Names must be unique within a dataset, and they can be reused across datasets.
In Python, expect_all, expect_all_or_drop and expect_all_or_fail take a dictionary of name to constraint and apply one action to the group. SQL can hold several expectations on a dataset but has no way to group them or load them dynamically. Rules can live in a Delta table or Python module, read by a helper function.
Choosing the action is a per-constraint decision, and the production checklist gives the rule: fail for conditions that should stop everything, such as a broken primary key; drop for records that can be discarded, with a quarantine table capturing what was dropped; warn only where someone actually watches the trend. Figure 5.2 shows what each action does to an invalid row, and how quarantine is built.
The scope of a failure depends on the pipeline mode.
| Pipeline mode | What expect_or_fail stops |
|---|---|
| Triggered | Only that flow, which rolls back; parallel flows carry on |
| Continuous | The flow and all dependent flows, with a message explaining why |
The EXPECTATION_VIOLATION error reports the violated expectation and, for many queries, the input record. Metrics are not recorded for fail, so read the message and fix the pipeline code before rerunning.
Expectations enforce quality, not orchestration. A validation table, such as a materialized view that compares row counts under expect_or_fail, does not gate its readers: a downstream dataset does not wait for its result. To stop downstream work when validation fails, put the validation and the downstream work in separate pipelines and link them with task dependencies in a job, as covered in “Lakeflow Jobs”.
Quarantine keeps bad rows for inspection without failing or losing them. A warn expectation records metrics, a computed column marks each row, and views split the two paths.
from pyspark import pipelines as dp
from pyspark.sql.functions import expr
rules = {
"valid_pickup": "(pickup_zip IS NOT NULL)",
"valid_dropoff": "(dropoff_zip IS NOT NULL)",
}
bad = "NOT({0})".format(" AND ".join(rules.values()))
@dp.table(temporary=True, partition_cols=["is_quarantined"])
@dp.expect_all(rules)
def trips_checked():
return (
spark.readStream.table("raw_trips")
.withColumn("is_quarantined", expr(bad))
)
@dp.view
def valid_trips():
return spark.read.table("trips_checked").filter(
"is_quarantined=false"
)
@dp.view
def invalid_trips():
return spark.read.table("trips_checked").filter(
"is_quarantined=true"
)edition must be ADVANCED to enforce expectations. ADVANCED is the default, PRO covers CDC, and CORE covers streaming ingest only.Common trap: Using
expect_or_dropwhen the bad rows may be needed later - dropped rows are gone and only a count is logged, so use the quarantine pattern to keep them.
Common trap: Relying on a validation table with
expect_or_failto block downstream tables in the same pipeline - in a triggered pipeline other flows continue to update, so the gate must be a job dependency between separate pipelines.
This note is one section of Ultra Transcenders DBX-DEP: Databricks Data Engineer Professional, an independent study guide that explains every topic the exam covers by technology, with comparison tables, diagrams and the common traps, plus a glossary linked to the Databricks documentation.
Due on Amazon in December 2026, in Kindle and paperback editions.
About the book · DBX-DEP terms in the glossary · All DBX-DEP study notes
Which compute fits each kind of workload, and the constraint that forces a move away from serverless.
How serverless, pipelines, classic jobs and bundles each get their Python libraries.
What a checkpoint holds and which query changes survive a restart.
How latency, cost and refresh decide between the two pipeline dataset types.
When each table layout fits, and what existing tables need.
How grants flow down from catalogs and schemas, and where they stop.
The steps that really remove a person's data, and why they can't run back to back.