
Data Quality pattern I landed on using dbt + DQX
dbt tests are a great CI gate, but they run after the model builds and only detect by the time one fails, the bad data is already in your table. For "keep the good rows, isolate the bad ones, keep going" pattern, you need row-level DQ that runs in-transit, and I adopted DQX as I use it with other Spark workloads too.
The thing I had to unlearn is that you do not rewrite your dbt SQL gold model as Python. The transformation logic stays in a normal `.sql` model, you just add a thin Python model beside it that dbt.ref()s it and applies DQX. The Python model (orders_gold_dq) becomes the published Gold table; the SQL model (orders_gold) becomes an internal intermediate. Your downstream consumers point to orders_gold_dq, not orders_gold.
-- models/orders_gold.sql
select order_id, customer_id, amount, status from {{ ref('orders_silver') }}
Thin DQ Layer:
# models/orders_gold_dq.py
from databricks.labs.dqx.engine import DQEngine
from databricks.sdk import WorkspaceClient
def model(dbt, session):
df = dbt.ref("orders_gold")
checks = [
{"criticality": "error", "check": {"function": "is_not_null",
"arguments": {"column": "order_id"}}},
{"criticality": "warn", "check": {"function": "is_in_list",
"arguments": {"column": "status", "allowed": ["new", "paid", "shipped"]}}},
{"criticality": "error", "check": {"function": "is_unique",
"arguments": {"columns": ["order_id"]}}},
]
dq = DQEngine(WorkspaceClient())
valid_df, quarantine_df = dq.apply_checks_by_metadata_and_split(df, checks)
return valid_df # clean rows become the published table
If your transformation is already a Python model (e.g. complex PySpark logic), you don't need the extra _dq.py layer at all, just embed DQX directly inside that model before the return:.
error rows → quarantine only, never written to the clean table; warn rows → stay in clean output with _warnings metadata, not quarantined. Each quarantined row carries _errors/_warnings with the rule + a readable message.
Wire DQX in as a serverless dep in dbt_project.yml (+submission_method: serverless_cluster,+environment_dependencies: [databricks-labs-dqx]), then just dbt run or use your preferred scheduling pattern. And I scheduled this on Databricks Lakeflow with a dbt task, as seen in the picture above.