What you will be able to do
- Declare a named expectation on a pipeline streaming table, materialized view, or view in SQL and Python
- Choose between the warn, drop, and fail actions according to how much invalid data a Silver or Gold dataset can accept
- Apply several expectations at once with the expect_all decorators, and know the SQL equivalent
- Find expectation metrics, and explain what a failing expectation does in triggered and continuous pipelines
Key concept
Expectation — An expectation is a named SQL Boolean condition attached to a pipeline dataset declaration. Every record is tested against it, and the expectation's action decides whether a failing record is kept, dropped, or stops the update.
1.Expectations: where quality rules go in a pipeline
In the medallion architecture the Silver layer is where data is checked. Databricks describes the layers as bronze (raw), silver (validated), and gold (enriched), and says of the Silver layer: "Data cleanup and validation are performed in this layer." Gold datasets are aggregated from Silver, so any bad record that gets through Silver ends up in Gold totals too. In Lakeflow pipelines the main tool for this job is the expectation.
An expectation is an optional clause on a pipeline materialized view, streaming table, or view declaration. It has three parts. The first is a name, which identifies the expectation in monitoring and must be unique within a dataset. You can reuse the same expectation on other datasets. The second is a constraint: a standard SQL Boolean expression that has to evaluate to true or false for each record. The third is an action, which decides what happens to a record that fails. In SQL the constraint goes in a CONSTRAINT name EXPECT (expr) clause. In Python it is a decorator on the dataset function. You can also use the SQL clause in CREATE STREAMING TABLE and CREATE MATERIALIZED VIEW when a standalone pipeline created in Databricks SQL backs them.
CREATE OR REFRESH STREAMING TABLE customers(
CONSTRAINT valid_customer_age EXPECT (age BETWEEN 0 AND 120)
) AS SELECT * FROM STREAM(datasets.samples.raw_customers);@dp.table
@dp.expect("valid_customer_age", "age BETWEEN 0 AND 120")
def customers():
return spark.readStream.table("datasets.samples.raw_customers")A constraint can use SQL functions, CASE statements, and compound Boolean logic. For example, year(transaction_date) >= 2020 or a CASE that allows different status values depending on the record type are both fine. A constraint cannot contain custom Python functions, external service calls, or subqueries that reference other tables. Each rule looks at one record at a time.
Checkpoint 1 of 8· Check yourself
A data engineer wants a Silver streaming table to reject orders whose customer_id is not in the customers table. Which proposed expectation constraint is NOT allowed?
Expectation constraints may not include subqueries that reference other tables. A cross-table check like this one needs a different pattern, such as a validation dataset built with a join (see the example after this question).
“Subqueries referencing other tables”Source: docs.databricks.com
Databricks' expectation patterns page shows how a cross-table check is built. Instead of a subquery inside the constraint, you declare a separate validation dataset that joins the two tables, and the constraint tests a column of the joined result. In this example a left outer join keeps every row of validation_copy, and r_key is null when no matching row exists in report, so r_key IS NOT NULL catches missing records. The example uses a fail expectation, but note that a validation table does not by itself gate other datasets: reading it from another dataset does not make that dataset wait for the validation result.
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.key2.The three actions: warn, drop, fail
The action is what turns a rule into a quality policy. Each action has a SQL form and a Python form. Warn keeps the data and records metrics. Drop filters out bad records and logs how many were removed. Fail treats any bad record as a blocking error.
| Action | SQL syntax | Python syntax | What happens to an invalid record |
|---|---|---|---|
| warn (default) | EXPECT | dp.expect | Written to the target along with valid records |
| drop | EXPECT ... ON VIOLATION DROP ROW | dp.expect_or_drop | Dropped before the write; the count of dropped records is logged with the dataset metrics |
| fail | EXPECT ... ON VIOLATION FAIL UPDATE | dp.expect_or_fail | Stops the update from succeeding; manual intervention is needed before reprocessing |
Pick the action according to what the dataset can tolerate. Use warn when you need visibility, for example while you are still learning how dirty a source is. Use drop when a Silver table must contain only usable rows, such as current_page_id IS NOT NULL AND current_page_title IS NOT NULL on clickstream data. Use fail when invalid records are unacceptable, for example a count that must be positive in a Gold aggregate. If you need to keep the bad rows somewhere without failing or dropping them, there is a separate quarantine pattern for that.
Checkpoint 2 of 8· Match them up
Match each expectation clause to its effect on a record that violates it
Tap a term, then the definition that fits it.
The three clauses correspond to the warn, drop, and fail actions. Only fail stops the update.
“Invalid records prevent the update from succeeding. Manual intervention is required before reprocessing.”Source: docs.databricks.com
Checkpoint 3 of 8· Fill the gap
Complete this SQL expectation so that a non-positive count stops the update.
CONSTRAINT valid_count EXPECT (count > 0) ON VIOLATION ? UPDATEThe fail action is written as ON VIOLATION FAIL UPDATE. DROP is only valid as DROP ROW.
Source: docs.databricks.comCheckpoint 4 of 8· Exam question
A Lakeflow Spark Declarative Pipeline loads raw sensor readings into a silver streaming table. Rows with a null `sensor_id` are unusable downstream, but the nightly update must keep running even when some rows fail this check. Which SQL constraint achieves this?
Correct answer: A — `CONSTRAINT valid_sensor_id EXPECT (sensor_id IS NOT NULL) ON VIOLATION DROP ROW` filters invalid rows out before the write and lets the rest of the update finish normally.
- A. The `DROP ROW` violation action removes rows that fail the constraint before they are written to the target, so bad rows are silently filtered while valid rows continue to flow and the update itself keeps running. This matches the requirement to keep nightly processing alive despite bad rows.
- B. `FAIL UPDATE` causes the entire pipeline update to stop the moment any row violates the constraint, which is the opposite of the 'keep running' requirement described in the scenario. It is meant for critical checks where any violation should halt the whole run.
- C. Omitting `ON VIOLATION` defaults to the warn behavior, where invalid records are still written to the target table and only tracked as a quality metric. That leaves the null `sensor_id` rows in the silver table, which does not satisfy the requirement.
- D. Pipeline expectations use the `EXPECT` keyword, not `CHECK`; `CHECK` is a different SQL constraint concept used elsewhere and is not recognized inside a declarative pipeline expectation definition, so this statement would not work as intended.
Sources2
3.Stacking several rules on one dataset
Real Silver tables need more than one rule, and the rules can differ in strictness. In Python you stack decorators. The dp module provides six of them: each of the three actions comes in a single-expectation version and an expect_all version that takes several expectations. The decorators go after the dataset decorator and before the function that defines the dataset.
from pyspark import pipelines as dp
@dp.table()
@dp.expect(description, constraint)
@dp.expect_or_drop(description, constraint)
@dp.expect_or_fail(description, constraint)
@dp.expect_all({description: constraint, ...})
@dp.expect_all_or_drop({description: constraint, ...})
@dp.expect_all_or_fail({description: constraint, ...})
def <function-name>():
return (<query>)The expect_all decorators take a dict that maps each description to its constraint. Each entry still reports its own metrics, so grouping rules does not hide which rule a record failed. SQL has no expect_all. You either list several comma-separated CONSTRAINT ... EXPECT clauses, or you combine conditions into one constraint with AND when they should share a single action and a single name.
Checkpoint 5 of 8· Check yourself
A Silver table uses @dp.expect_all_or_drop with a dict of three rules. A record fails one of the three. What do the metrics show?
Grouping rules in an expect_all dict does not merge their metrics. Each expectation keeps its own description and reports its own counts.
“When you use expect_all decorators, each expectation has its own description and reports granular metrics.”Source: docs.databricks.com
Combine the two conditions with AND inside one CONSTRAINT and add ON VIOLATION FAIL UPDATE. Databricks' own schema-evolution example does exactly this, noting that expect_all is a Python-specific API. The cost is that the event log then shows one expectation name instead of two.
4.Reading metrics and handling a failed update
Warn and drop expectations both produce tracking metrics, which you can view in the pipeline UI. Fail does not, because the update stops before anything is recorded. To see the metrics in the UI, click Jobs & Pipelines in the workspace sidebar, click the name of your pipeline, click a dataset that has an expectation defined, and select the Data quality tab in the right sidebar. Streaming tables and materialized views backed by a standalone Databricks SQL pipeline have no Data quality tab, so you query the pipeline event log for their metrics instead. The event log holds the metrics for any Lakeflow pipeline, and the expectation name you chose is how entries are told apart: Databricks' quarantine example notes that merging conditions into one constraint gives a single name in the event log, while separate constraints give multiple entries under different names. The documentation points to its page on querying data quality or expectations metrics for the exact queries.
Checkpoint 6 of 8· Put it in order
A continuous pipeline's expect_or_fail expectation meets an invalid record. Put what follows in order.
- 1.For a table update, the transaction is rolled back atomically
- 2.The expectation detects the invalid record and execution stops immediately
- 3.You fix the pipeline code to handle the invalid data, then re-run the pipeline
- 4.The flow, all dependent flows, and the pipeline stop, and a message explains why
Fail stops execution at the first invalid record, a table update is rolled back, and in continuous mode the stop spreads to dependent flows. Re-running only makes sense after the code handles the invalid data.
“you must fix the pipeline code to handle the invalid data correctly before re-running the pipeline”Source: docs.databricks.com
When a fail expectation is violated, execution stops immediately. If the operation is a table update, the transaction is rolled back atomically, so the target never shows a half-written state. How far the failure spreads depends on the run mode. In a triggered pipeline, only the failing flow fails and parallel flows continue. In a continuous pipeline, the failure stops the flow and every flow that depends on it, and the pipeline emits a message saying why it stopped. In both modes you have to fix the pipeline code so it handles the invalid data before you re-run. Fail expectations also change the query plan so that violations can be traced. The resulting EXPECTATION_VIOLATION error message names the violated expectation and can show the input record and the output record involved.
Checkpoint 7 of 8· Check yourself
A triggered pipeline has two independent flows. One flow's expect_or_fail expectation is violated during an update. What is the result?
In triggered mode the failure is limited to the offending flow. The pipeline-wide stop applies to continuous pipelines.
“In a triggered pipeline, the failure of a single flow does not cause other parallel flows to fail.”Source: docs.databricks.com
Checkpoint 8 of 8· Exam question
When a pipeline expectation constraint is defined without specifying an `ON VIOLATION` clause, what is the default behavior for rows that fail the check?
Correct answer: A — Invalid rows are still written to the target, and the violation is only tracked as a data quality metric in the event log.
- A. The default action for an expectation with no explicit `ON VIOLATION` clause is the warn behavior: rows that fail the check are still written to the target, and the failure is only surfaced through data quality metrics for monitoring. No rows are removed or blocked.
- B. Dropping invalid rows before the write is the behavior of the explicit `DROP ROW` violation action, not the default. Without specifying an action, rows are retained rather than filtered out.
- C. Failing the entire update is the behavior of the explicit `FAIL UPDATE` action, which must be stated explicitly. It is not what happens when no violation action is configured.
- D. Expectations never create a separate quarantine table automatically; any quarantine pattern has to be built by the engineer as a second dataset with an inverted condition, it is not a built-in default action.
Sources2
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.An EXPECT clause with no ON VIOLATION keeps invalid rows out of the target table.Why is that wrong?
Without ON VIOLATION the action is warn: invalid records are written to the target, and only the metrics record the violations. To keep them out you need DROP ROW or FAIL UPDATE.
Covered in The three actions: warn, drop, fail
2.After an expect_or_fail violation, you can check the Data quality tab to see how many records failed.Why is that wrong?
Fail records no metrics, because the update stops when the first invalid record is detected. Only warn and drop produce tracking metrics.
Covered in Reading metrics and handling a failed update
3.A failed expectation always stops the entire pipeline.Why is that wrong?
That only happens in continuous mode, where the failure stops the flow and its dependent flows. In a triggered pipeline, the other parallel flows keep running.
Covered in Reading metrics and handling a failed update
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Data cleanup and validation are performed in this layer.”
↩︎ Expectations: where quality rules go in a pipeline - 2.https://docs.databricks.com/aws/en/ldp/expectationsOfficial docs
“Expectations use standard SQL Boolean statements to specify constraints.”
↩︎ Expectations: where quality rules go in a pipeline“An expectation name must be unique for a given dataset.”
↩︎ Expectations: where quality rules go in a pipeline“Retaining invalid records is the default behavior for expectations.”
↩︎ The three actions: warn, drop, fail“the Data quality tab in the pipeline UI is not available. Query the event log to view expectation metrics.”
↩︎ Reading metrics and handling a failed update“If the operation is a table update, the system atomically rolls back the transaction”
↩︎ Reading metrics and handling a failed update“you must fix the pipeline code to handle the invalid data correctly before re-running the pipeline”
↩︎ Reading metrics and handling a failed update“Select the Data quality tab in the right sidebar.”
↩︎ Reading metrics and handling a failed update“apply data quality checks on each record passing through a query”
↩︎ Key concept“Invalid records are written to the target.”
↩︎ Exam trap 1“Because fail causes the update to fail when an invalid record is detected, metrics are not recorded.”
↩︎ Exam trap 2“an expectation failure stops the flow, all dependent flows”
↩︎ Exam trap 3“Subqueries referencing other tables”
↩︎ Checkpoint“Invalid records prevent the update from succeeding. Manual intervention is required before reprocessing.”
↩︎ Checkpoint“In a triggered pipeline, the failure of a single flow does not cause other parallel flows to fail.”
↩︎ Checkpoint - 3.
“A validation table doesn't gate its downstream tables.”
↩︎ Expectations: where quality rules go in a pipeline“Merging multiple constraints into one as expect_all is Python-specific API”
↩︎ Stacking several rules on one dataset - 4.
“When you use expect_all decorators, each expectation has its own description and reports granular metrics.”
↩︎ Stacking several rules on one dataset