Data Quality pattern I landed on using dbt + DQX
▲ 74 r/databricks+1 crossposts

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.

u/zr-brickster — 4 days ago

pivot() workarounds in Spark Declarative Pipelines

Problem: In Spark Declarative Pipelines, the pivot() function is not supported. The pivot operation in Spark requires the eager loading of input data to compute the output schema. This capability is not supported in pipelines.

Source: https://spark.apache.org/docs/latest/declarative-pipelines-programming-guide.html#important-considerations

How can this be mitigated?

Workaround 1: Rewrite PIVOT Using CASE WHEN

This is the most common workaround. You manually expand the pivot into conditional aggregations.

SELECT *
FROM sales_data
PIVOT (
SUM(sales)
FOR region IN ('North', 'South', 'East', 'West')
)

SELECT
product,
SUM(CASE WHEN region = 'North' THEN sales ELSE 0 END) AS North,
SUM(CASE WHEN region = 'South' THEN sales ELSE 0 END) AS South,
SUM(CASE WHEN region = 'East'  THEN sales ELSE 0 END) AS East,
SUM(CASE WHEN region = 'West'  THEN sales ELSE 0 END) AS West
FROM sales_data
GROUP BY product

This works perfectly in Spark Declarative Pipelines because the output schema is fully deterministic at parse time, no eager data loading required.

Workaround 2: Rewrite PIVOT Using aggregate FILTER

Databricks SQL supports the FILTER(WHERE ...) clause on aggregates, which is a cleaner alternative to CASE WHEN:

SELECT year, region, q1, q2, q3, q4
FROM sales
PIVOT (
SUM(sales) AS sales
FOR quarter IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4)
)

SELECT
year,
region,
SUM(sales) FILTER(WHERE quarter = 1) AS q1,
SUM(sales) FILTER(WHERE quarter = 2) AS q2,
SUM(sales) FILTER(WHERE quarter = 3) AS q3,
SUM(sales) FILTER(WHERE quarter = 4) AS q4
FROM sales
GROUP BY year, region

This syntax is often more readable than nested CASE WHEN, especially with multiple aggregations.

Multi-Column PIVOT Rewrite

SELECT *
FROM sales
PIVOT (
SUM(sales) AS sales
FOR (quarter, region)
IN ((1, 'east') AS q1_east, (1, 'west') AS q1_west,
(2, 'east') AS q2_east, (2, 'west') AS q2_west)
)

SELECT
year,
SUM(sales) FILTER(WHERE quarter = 1 AND region = 'east') AS q1_east,
SUM(sales) FILTER(WHERE quarter = 1 AND region = 'west') AS q1_west,
SUM(sales) FILTER(WHERE quarter = 2 AND region = 'east') AS q2_east,
SUM(sales) FILTER(WHERE quarter = 2 AND region = 'west') AS q2_west
FROM sales
GROUP BY year

Multiple Aggregations

You can also rewrite PIVOTs that use multiple aggregate functions.

SELECT *
FROM (SELECT year, quarter, sales FROM sales) AS s
PIVOT (
SUM(sales) AS total, AVG(sales) AS avg
FOR quarter IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4)
)

SELECT
year,
SUM(sales) FILTER(WHERE quarter = 1) AS q1_total,
AVG(sales) FILTER(WHERE quarter = 1) AS q1_avg,
SUM(sales) FILTER(WHERE quarter = 2) AS q2_total,
AVG(sales) FILTER(WHERE quarter = 2) AS q2_avg,
SUM(sales) FILTER(WHERE quarter = 3) AS q3_total,
AVG(sales) FILTER(WHERE quarter = 3) AS q3_avg,
SUM(sales) FILTER(WHERE quarter = 4) AS q4_total,
AVG(sales) FILTER(WHERE quarter = 4) AS q4_avg
FROM sales
GROUP BY year
reddit.com
u/zr-brickster — 5 days ago
▲ 2 r/databricks+1 crossposts

Zero standing privilege in Azure Databricks with PIM + AIM: Actual Patterns

Entra PIM (Privileged Identity Management) and Databricks AIM (Automatic Identity Management) work well together to eliminate standing privilege in Azure Databricks.

Instead of permanently assigning privileged access, you put Databricks permissions on Entra security groups and let PIM control who can activate membership in those groups, and for how long. AIM then picks up that group membership from Entra and applies the Databricks permissions tied to it.

Note: AIM refreshes group membership on auth/authorization events, so browser-based access usually updates faster than token- or job-based access.

That makes this pattern especially useful in a few scenarios:

1) Break-glass workspace admin access

Example:

  • Group: databricks-prod-breakglass-admin
  • Databricks permission: workspace admin on the production workspace

Normal state: nobody has standing admin access.

Incident state: an on-call engineer activates the PIM assignment, gets temporary admin access to the workspace, does the required troubleshooting, and then loses that access automatically when the activation expires.

That is much cleaner than leaving a broad admin group permanently active.

2) Temporary production support access

A lot of teams need occasional elevated access for prod debugging, pipeline validation, or incident triage, but not full-time access.

Example:

  • Group: databricks-prod-support
  • Databricks permission: access to the prod workspace with scoped access to relevant jobs and compute

This works well for support engineers, platform teams, or on-call rotations that need short-lived access during a support window.

3) Time-bound access to sensitive Unity Catalog data

This is another very strong use case, especially for regulated or high-sensitivity datasets.

Example:

  • Group: databricks-sensitive-data-readers
  • Databricks permission: USE CATALOG, USE SCHEMA, SELECT on sensitive tables or schemas
  • PIM policy: approval, justification, and limited activation duration

Instead of giving analysts or engineers long-lived access to sensitive data, they activate it only when needed. When the PIM window ends, Entra removes the group membership, and Databricks reflects that change on a subsequent auth or authorization-triggering event.

Why AIM makes this easier

AIM uses Entra as the source of truth and refreshes group memberships during authentication and authorization events such as browser logins, token authentication, and job runs. So the operating model becomes:

  • assign Databricks permissions to Entra groups
  • manage eligibility in PIM
  • let users activate access only when needed
  • let Databricks pick up the membership change through AIM

Entra PIM controls who can elevate, AIM reflects that group membership into Databricks, and Databricks permissions remain group-based instead of user-based.

u/zr-brickster — 28 days ago

Problem: In Lakeflow Spark Declarative Pipelines, the pivot() function is not supported. The pivot operation in Spark requires the eager loading of input data to compute the output schema. This capability is not supported in pipelines.

Source: https://docs.databricks.com/aws/en/ldp/limitations

How can this be mitigated?

Workaround 1: Rewrite PIVOT Using CASE WHEN

This is the most common workaround. You manually expand the pivot into conditional aggregations.

>Original Query:

SELECT *
FROM sales_data
PIVOT (
  SUM(sales) 
  FOR region IN ('North', 'South', 'East', 'West')
)

>Rewritten without PIVOT:

SELECT 
  product,
  SUM(CASE WHEN region = 'North' THEN sales ELSE 0 END) AS North,
  SUM(CASE WHEN region = 'South' THEN sales ELSE 0 END) AS South,
  SUM(CASE WHEN region = 'East'  THEN sales ELSE 0 END) AS East,
  SUM(CASE WHEN region = 'West'  THEN sales ELSE 0 END) AS West
FROM sales_data
GROUP BY product

This works perfectly in Lakeflow Pipelines because the output schema is fully deterministic at parse time, no eager data loading required.

Workaround 2: Rewrite PIVOT Using aggregate FILTER

Databricks SQL supports the FILTER(WHERE ...) clause on aggregates, which is a cleaner alternative to CASE WHEN:

>Original PIVOT query:

SELECT year, region, q1, q2, q3, q4
FROM sales
PIVOT (
  SUM(sales) AS sales
  FOR quarter IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4)
)

>Rewritten with FILTER:

SELECT 
  year, 
  region,
  SUM(sales) FILTER(WHERE quarter = 1) AS q1,
  SUM(sales) FILTER(WHERE quarter = 2) AS q2,
  SUM(sales) FILTER(WHERE quarter = 3) AS q3,
  SUM(sales) FILTER(WHERE quarter = 4) AS q4
FROM sales
GROUP BY year, region

This syntax is often more readable than nested CASE WHEN, especially with multiple aggregations.

Multi-Column PIVOT Rewrite

>For pivoting on multiple columns simultaneously:

SELECT *
FROM sales
PIVOT (
  SUM(sales) AS sales
  FOR (quarter, region) 
  IN ((1, 'east') AS q1_east, (1, 'west') AS q1_west, 
      (2, 'east') AS q2_east, (2, 'west') AS q2_west)
)

>Rewritten:

SELECT 
  year,
  SUM(sales) FILTER(WHERE quarter = 1 AND region = 'east') AS q1_east,
  SUM(sales) FILTER(WHERE quarter = 1 AND region = 'west') AS q1_west,
  SUM(sales) FILTER(WHERE quarter = 2 AND region = 'east') AS q2_east,
  SUM(sales) FILTER(WHERE quarter = 2 AND region = 'west') AS q2_west
FROM sales
GROUP BY year

Multiple Aggregations

You can also rewrite PIVOTs that use multiple aggregate functions.

>Original Query

SELECT *
FROM (SELECT year, quarter, sales FROM sales) AS s
PIVOT (
  SUM(sales) AS total, AVG(sales) AS avg
  FOR quarter IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4)
)

>Rewritten:

SELECT 
  year,
  SUM(sales) FILTER(WHERE quarter = 1) AS q1_total,
  AVG(sales) FILTER(WHERE quarter = 1) AS q1_avg,
  SUM(sales) FILTER(WHERE quarter = 2) AS q2_total,
  AVG(sales) FILTER(WHERE quarter = 2) AS q2_avg,
  SUM(sales) FILTER(WHERE quarter = 3) AS q3_total,
  AVG(sales) FILTER(WHERE quarter = 3) AS q3_avg,
  SUM(sales) FILTER(WHERE quarter = 4) AS q4_total,
  AVG(sales) FILTER(WHERE quarter = 4) AS q4_avg
FROM sales
GROUP BY year

Summary

Both approaches produce identical results and work fully within SDP pipelines with complete lineage tracking.

u/zr-brickster — 2 months ago

How do business users at your company find the right asset for a specific domain?

Databricks has a feature for this: Discover + Domains. It’s currently in Beta, and it gives you a curated, business-friendly way to organize and browse assets.

https://preview.redd.it/pii6h72fnhzg1.png?width=3452&format=png&auto=webp&s=ee64e06ee878ceb10c4bc6bf7219d62c096531cd

What’s useful about it:

  • Organize by business concept
    • Domains let you group assets around concepts like Finance, Marketing, or Customer Support instead of making users navigate catalog/schema/table names.
  • Curated discovery
    • Curators can create and highlight custom sections on both the main Discover page and on each domain page, so you can feature things like Key Metrics, Quarterly Reports, or Getting Started.
  • Governed tags keep the taxonomy clean
    • Domains are built on governed tags, so you can standardize domain labels and control who can assign them. That’s much better than ending up with random variants like finance, Finance, and fin floating around.
  • It sits above the catalog hierarchy
    • In practice, that means you can bring together catalog assets like tables and metric views, plus assets like dashboards, notebooks, and Genie Spaces under one business concept.

https://preview.redd.it/yub3p9pbnhzg1.png?width=1142&format=png&auto=webp&s=a6a84e080e399ab1f9288b2c6a36b743e15a7e9b

Who should care:

  • Data producers: publish important assets into business-facing domains.
  • Business users: a better entry point than memorizing technical paths or asking around.

https://preview.redd.it/1s1iv769fhzg1.png?width=2184&format=png&auto=webp&s=718e4fb5563c5d63168e5d78837f54446772603d

https://preview.redd.it/6xrc0odwfhzg1.png?width=2150&format=png&auto=webp&s=d24597e453206ca609b67a469f899073aec33962

A lot of data platform friction is coming from the fact that people can’t find the right thing fast enough. And that's why I like this feature, as it’s one of those platform capabilities that can dramatically improve user experience.

reddit.com
u/zr-brickster — 2 months ago

Situation

You've got 30, 50, maybe 100 jobs in your Declarative Automation Bundle. Every single one needs failure notifications. Every single one needs cost-center tags. Every single one needs the right cluster policy. And every time someone adds a new job, they forget at least one of those things.

You could write one Python function that enforces it automatically at deploy time. That's what DABs Python mutators do.

What Are Mutators?

A mutator is a Python function that runs during databricks bundle deploy. It receives every job (or pipeline) in your bundle, whether defined in YAML or Python, and returns a modified copy. Think of it as middleware for your deployment config.

Write a tag, permission, or compute standard once, and apply it automatically to every resource at deploy time. No drift.

Decorate a function with  `@job_mutator`, `@pipeline_mutator`,  `@schema_mutator`, or `@volume_mutator`.

The function receives the resource + bundle context, and returns a transformed copy.

You register them in databricks.yml:

python:
  mutators: 
  - 'mutators:add_pipeline_mutators'

Example

This example defines common pipeline standards for every pipeline in your bundle:

  • Specifies common tags.
  • Enforces serverless compute.
  • Defines default notifications group and when to trigger an alert.

​

from databricks.bundles.core import Bundle, pipeline_mutator

@pipeline_mutator 
def add_pipeline_mutators(bundle: Bundle, p: Pipeline) -> Pipeline:    
  p = replace(p, tags=_add_common_tags(bundle, p.tags))    
  p = replace(p, serverless=True)    
  default = Notifications.from_dict(       
    { 
      "email_recipients": "${var.recipients}",         
      "alerts": ["on-update-failure", "on-update-fatal-failure", "on-flow-failure"] 
    }    
  )    
  p = replace(p, notifications=[default])    
  return p

Other resources:

Use Cases

https://preview.redd.it/2v2ikiexd4yg1.png?width=632&format=png&auto=webp&s=fa39246e3830b857f1da43777aacfb1079a261a8

Job Mutator Examples:

  • Enforce default email notifications, owners, tags.
  • Standardize job clusters / serverless environments.
  • Inject common job parameters or health/queue settings.

Pipeline Mutator Examples:

  • Enforce pipeline cluster / environment settings.
  • Apply consistent configuration, catalog/schema, or triggers across all pipelines.

Schema Mutator Examples:

  • Apply standard permissions or tags to all schemas.
  • Enforce naming conventions or lifecycle settings.

Volume Mutators:

  • Set default storage locations, ACLs, or lifecycle flags.
  • Add org‑wide tags or conventions to all volumes.
reddit.com
u/zr-brickster — 2 months ago