<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic DQX - Everything The Docs Don’t Say Yet in Community Articles</title>
    <link>https://community.databricks.com/t5/community-articles/dqx-everything-the-docs-don-t-say-yet/m-p/169166#M1580</link>
    <description>&lt;P class=""&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;DQX Studio already has its own documentation for the rule editor, the approval workflow, the scheduler. This is what running it against real pipelines, real approvers, and a growing quarantine table made us add on top of it — and around it. It'll be out of date the next time we ship something, and that's fine.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;A href="https://databrickslabs.github.io/dqx/" target="_blank" rel="noopener"&gt;DQX&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;gives you check functions and an engine. DQX Studio — the UI and control plane we run on top of it — gives you a place to author, approve, schedule, and review those checks without writing a notebook. Studio itself is documented elsewhere; this isn’t that document.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;This is the list of things that turned out to be missing once real people, not just a pipeline, depended on it. An analyst who wants to see the data before writing a filter. An approver who wants to know why a rule looks the way it does, six weeks later. A scheduler that has to decide, unattended, whether “the last hour” means anything for a given table. A quarantine table that needs an owner other than whoever happens to open it.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What follows, roughly in the order we hit them.&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_0-1789797084706.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31314iE74E870298014204/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_0-1789797084706.png" alt="KeerthiItigimat_0-1789797084706.png" /&gt;&lt;/span&gt;&lt;STRONG&gt;Figure 1.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;Three layers, one API contract. Studio's own docs cover the middle layer; this post is the outer one.&lt;/SPAN&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;/DIV&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;01&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Preview the data before you write anything&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Every rule starts with someone looking at the table. The preview panel in the rule editor runs a live sample against Unity Catalog under the caller’s own identity — on-behalf-of, so row filters and column masks apply the same way they would to any other query that person runs — with two things layered on:&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;A column picker&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to narrow a wide table down to what’s relevant before you start authoring a check against it.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;A natural-language filter box.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Type “orders from the last week with a null customer_id” and it’s handed to a Foundation Model with the table’s real column list and a hard constraint — use only these columns, always cap the row count, default to a plain&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;SELECT *&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if the request doesn’t resolve to anything sensible. The model returns SQL, not an answer; if the call fails for any reason, the preview falls back to an ordinary unfiltered sample rather than erroring out.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Small feature, but it collapses the gap between “I can describe the bad rows” and “I can write a SQL predicate for them” — which is most of the distance between a domain expert and a rule.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;02&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Two ways to run a check, on purpose&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Databricks Apps don’t run Spark. So every dry run has to leave the app process one way or another — and we deliberately built two different exits for it, because “fast” and “exactly how it’ll run in production” are different needs.&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Run Locally&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;executes against a small sample through a local, serverless Spark session — a few seconds, no job to wait on. Good for iterating on a rule while you’re still writing it.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Run as Workflow&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;submits the same check to the actual task-runner job — the identical path a schedule will use in production, including the real sample size and Row Scope. Slower, but it’s not a simulation of the production path, it&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;is&lt;/EM&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;the production path.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;An author uses the first ten times while shaping a rule and the second once, right before submitting it for approval, to confirm it survives contact with the real execution environment.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;03&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Every rule remembers how it was made&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;A rule authored six weeks ago, by someone else, using a phrase like “flag orders where the region code doesn’t match our list” — an approver reviewing that later shouldn’t have to reverse-engineer intent from a YAML blob. Two things carry that context forward: a full audit trail (dq_quality_rules_history) recording every version, every status transition, and who made it, not just the current state; and, for AI-generated checks specifically, the original natural-language instruction travels with the rule as metadata rather than being discarded once the check function comes back. The prompt that produced a rule is part of the rule’s own record, not a one-time scratch input.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;04&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Global rules, for the check that isn’t about one table&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Most checks are bound to a table —&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;orders.customer_id is not null. Some aren’t really about a table at all; they’re about a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;shape&lt;/EM&gt;: “any column named&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;email&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;should look like an email,” regardless of which of forty tables it shows up in. Global rules are authored once, table-independent, and apply wherever a matching column exists — without re-authoring the same check forty times or drifting slightly different each time someone copies it.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;05&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Cross-table checks, and what production found&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;DQX’s&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;sql_query&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;check is the right tool for anything that isn’t row-local — a duplicate key, a referential mismatch between two tables. It also has a real contract: an explicit&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;condition&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;column, and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;merge_columns&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to join violations back to specific input rows. An author writing “give me the rows that violate this” doesn’t know that contract, and shouldn’t have to.&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;what an author writes vs. what DQX's engine requires&lt;/FONT&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_1-1789797204606.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31315i997A702D28713AD9/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_1-1789797204606.png" alt="KeerthiItigimat_1-1789797204606.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;The editor derives that wrap on save, and only trusts a column as a merge key once it’s checked against the target table’s&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;real&lt;/EM&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;schema — a renamed join column looks safe in the query text and fails at execution time, so schema-text alone isn’t enough. A related fix went the other way: for a query that doesn’t actually depend on its home table’s rows (its own independent join), the engine was pre-sampling that input before the check ran, which could silently drop real violations whose key wasn’t in the sampled slice. The fix routes that shape through the same fast path a true cross-table check already used, instead of teaching the row-level engine a special case.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Underneath both: Unity Catalog metadata reaching a fresh serverless job can lag well behind a SQL warehouse seeing the same object, so the read path needed a retry budget sized off actual observed timelines, not a guess.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;06&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Alerting that isn’t just “check the app”&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;A validation result nobody sees is worth nothing. Two channels out:&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Microsoft Teams&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;webhooks, scoped per channel by trigger — all runs, scheduled-only, or manual-only — so a channel used for production monitoring doesn’t fill up with every dry run an author fires off while iterating.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Site24x7&lt;/STRONG&gt;, or any uptime monitor, via a pair of read-only status endpoints — latest result by table, or by a specific run id — that return a plain 200 on a clean run and a 503 the moment error rows show up. External monitoring infrastructure gets to treat data quality the same way it treats an HTTP health check, without knowing anything about DQX.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;07&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Schedules that know what they’re sampling&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;“Validate the last hour” only means something if the scheduler knows which column is time, and on a table it’s never seen before, that’s a real guess. It tries typed timestamp/date columns first, then a name-priority list, and surfaces the candidates as editable tags — a schedule owner can pin the right one, remove one that’s wrong for their table, or leave it on automatic. Row Scope pairs with tag-based scope filtering, so one schedule can target “every approved rule labeled&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;pii” across every table that has one, on either the full table or a time-windowed sample, instead of one schedule per table.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;08&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Closing the loop, not just quarantining&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Everything above eventually produces failing rows, and Studio’s side of what happens to them is deliberately small: a per-dataset YAML runbook, edited or uploaded in the app, written straight to a Unity Catalog Volume. The app checks that it’s valid YAML and writes it — it doesn’t parse rule names, strategies, or parameters, so a new remediation strategy is a change to the consuming pipeline, never to Studio.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What reads those runbooks — an agentic re-ingestion system that tries the documented fix first and falls back to an LLM only for what nobody has written a playbook for — is its own build, written up separately:&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;A href="https://keerthimath.github.io/blog/agent-proposes-dqx-disposes/" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Agent Proposes, DQX Disposes →&lt;/STRONG&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;09&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;dqx-validator — the write side of the dead-letter pattern&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Everything so far assumes rows are already in a Silver table by the time DQX sees them.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;dqx-validator&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is the piece that runs earlier — a standalone library an ingestion pipeline calls directly, mid-flight, to apply Studio’s own approved rules to a DataFrame before it lands, and quarantine whatever fails.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;It loads&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;status = 'approved'&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;checks from the same rules tables Studio’s approval workflow writes to, resolved per-environment from a small table config. The loader is exposed as a single function,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;approved_checks_for(table_fqns), and the agentic re-ingestion side calls the exact same function — so the checks that quarantined a row and the checks that later re-validate a proposed fix are guaranteed to be the same object, not two copies that can drift.&lt;BR /&gt;&lt;BR /&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_3-1789797710611.png" style="width: 708px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31317i40D76A52F8CD8492/image-dimensions/708x209?v=v2" width="708" height="209" role="button" title="KeerthiItigimat_3-1789797710611.png" alt="KeerthiItigimat_3-1789797710611.png" /&gt;&lt;/span&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Splitting the DataFrame is one call to DQX’s own&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;apply_checks_by_metadata_and_split&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;— no custom validation logic, just the public engine. The quarantine row carries the input schema unchanged plus bookkeeping columns (&lt;/SPAN&gt;_error&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_warning&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_generated_at&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_data_source&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_rule_sources&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_is_cross_table&lt;SPAN&gt;), and one more that matters more than it looks:&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_row_id&lt;SPAN&gt;, a hash of whichever business-key columns the caller names. That’s the field the entire review queue on the read side keys every quarantined row on. Name the wrong columns, or none, and the hash falls back to every input column — which still works, but logs a warning, because at that point two rows differing only in a bookkeeping timestamp look like two different violations instead of one.&lt;/SPAN&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_2-1789797607909.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31316i946E6220F74AF4AF/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_2-1789797607909.png" alt="KeerthiItigimat_2-1789797607909.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#808080"&gt;&lt;STRONG&gt;Figure 2.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Items 08 and 09 are two ends of the same loop — dqx-validator writes to quarantine, the remediation pipeline reads it back out, and both sides resolve rules the same way.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;10&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Ask it directly, instead of building another chart&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Studio has an Insights dashboard for the questions we anticipated. It doesn’t have one for the question someone asks once, in passing — “which tables had a spike in errors last Tuesday” isn’t worth a dashboard tile, but it’s a completely reasonable thing to want to know. A Genie space scoped to Studio’s own schema — run history, quarantine records, metrics — turns that into a conversation instead of a feature request: ask in plain language, get an answer grounded in the real tables, no ticket filed to add a chart nobody will look at again.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;11&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What all of this has in common&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;None of the ten items above touch the DQX library. Every one of them is a caller of its public API, the same way a notebook is — the editor, the validator library, the agentic pipeline, all of it sits beside DQX rather than inside a fork of it. That was never an accident; it’s the only way any of this stays upgradeable as the library keeps moving.&lt;/FONT&gt;&lt;/P&gt;&lt;P class=""&gt;&lt;STRONG&gt;&lt;FONT face="georgia,palatino" size="4" color="#008080"&gt;Studio is documented. This is everything the documentation doesn’t say yet.&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;</description>
    <pubDate>Sat, 19 Sep 2026 06:04:43 GMT</pubDate>
    <dc:creator>KeerthiItigimat</dc:creator>
    <dc:date>2026-09-19T06:04:43Z</dc:date>
    <item>
      <title>DQX - Everything The Docs Don’t Say Yet</title>
      <link>https://community.databricks.com/t5/community-articles/dqx-everything-the-docs-don-t-say-yet/m-p/169166#M1580</link>
      <description>&lt;P class=""&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;DQX Studio already has its own documentation for the rule editor, the approval workflow, the scheduler. This is what running it against real pipelines, real approvers, and a growing quarantine table made us add on top of it — and around it. It'll be out of date the next time we ship something, and that's fine.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;A href="https://databrickslabs.github.io/dqx/" target="_blank" rel="noopener"&gt;DQX&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;gives you check functions and an engine. DQX Studio — the UI and control plane we run on top of it — gives you a place to author, approve, schedule, and review those checks without writing a notebook. Studio itself is documented elsewhere; this isn’t that document.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;This is the list of things that turned out to be missing once real people, not just a pipeline, depended on it. An analyst who wants to see the data before writing a filter. An approver who wants to know why a rule looks the way it does, six weeks later. A scheduler that has to decide, unattended, whether “the last hour” means anything for a given table. A quarantine table that needs an owner other than whoever happens to open it.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What follows, roughly in the order we hit them.&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_0-1789797084706.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31314iE74E870298014204/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_0-1789797084706.png" alt="KeerthiItigimat_0-1789797084706.png" /&gt;&lt;/span&gt;&lt;STRONG&gt;Figure 1.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;Three layers, one API contract. Studio's own docs cover the middle layer; this post is the outer one.&lt;/SPAN&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;/DIV&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;01&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Preview the data before you write anything&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Every rule starts with someone looking at the table. The preview panel in the rule editor runs a live sample against Unity Catalog under the caller’s own identity — on-behalf-of, so row filters and column masks apply the same way they would to any other query that person runs — with two things layered on:&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;A column picker&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to narrow a wide table down to what’s relevant before you start authoring a check against it.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;A natural-language filter box.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Type “orders from the last week with a null customer_id” and it’s handed to a Foundation Model with the table’s real column list and a hard constraint — use only these columns, always cap the row count, default to a plain&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;SELECT *&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if the request doesn’t resolve to anything sensible. The model returns SQL, not an answer; if the call fails for any reason, the preview falls back to an ordinary unfiltered sample rather than erroring out.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Small feature, but it collapses the gap between “I can describe the bad rows” and “I can write a SQL predicate for them” — which is most of the distance between a domain expert and a rule.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;02&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Two ways to run a check, on purpose&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Databricks Apps don’t run Spark. So every dry run has to leave the app process one way or another — and we deliberately built two different exits for it, because “fast” and “exactly how it’ll run in production” are different needs.&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Run Locally&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;executes against a small sample through a local, serverless Spark session — a few seconds, no job to wait on. Good for iterating on a rule while you’re still writing it.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Run as Workflow&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;submits the same check to the actual task-runner job — the identical path a schedule will use in production, including the real sample size and Row Scope. Slower, but it’s not a simulation of the production path, it&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;is&lt;/EM&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;the production path.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;An author uses the first ten times while shaping a rule and the second once, right before submitting it for approval, to confirm it survives contact with the real execution environment.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;03&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Every rule remembers how it was made&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;A rule authored six weeks ago, by someone else, using a phrase like “flag orders where the region code doesn’t match our list” — an approver reviewing that later shouldn’t have to reverse-engineer intent from a YAML blob. Two things carry that context forward: a full audit trail (dq_quality_rules_history) recording every version, every status transition, and who made it, not just the current state; and, for AI-generated checks specifically, the original natural-language instruction travels with the rule as metadata rather than being discarded once the check function comes back. The prompt that produced a rule is part of the rule’s own record, not a one-time scratch input.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;04&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Global rules, for the check that isn’t about one table&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Most checks are bound to a table —&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;orders.customer_id is not null. Some aren’t really about a table at all; they’re about a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;shape&lt;/EM&gt;: “any column named&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;email&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;should look like an email,” regardless of which of forty tables it shows up in. Global rules are authored once, table-independent, and apply wherever a matching column exists — without re-authoring the same check forty times or drifting slightly different each time someone copies it.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;05&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Cross-table checks, and what production found&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;DQX’s&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;sql_query&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;check is the right tool for anything that isn’t row-local — a duplicate key, a referential mismatch between two tables. It also has a real contract: an explicit&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;condition&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;column, and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;merge_columns&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to join violations back to specific input rows. An author writing “give me the rows that violate this” doesn’t know that contract, and shouldn’t have to.&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;DIV class=""&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;what an author writes vs. what DQX's engine requires&lt;/FONT&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_1-1789797204606.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31315i997A702D28713AD9/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_1-1789797204606.png" alt="KeerthiItigimat_1-1789797204606.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;The editor derives that wrap on save, and only trusts a column as a merge key once it’s checked against the target table’s&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;real&lt;/EM&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;schema — a renamed join column looks safe in the query text and fails at execution time, so schema-text alone isn’t enough. A related fix went the other way: for a query that doesn’t actually depend on its home table’s rows (its own independent join), the engine was pre-sampling that input before the check ran, which could silently drop real violations whose key wasn’t in the sampled slice. The fix routes that shape through the same fast path a true cross-table check already used, instead of teaching the row-level engine a special case.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Underneath both: Unity Catalog metadata reaching a fresh serverless job can lag well behind a SQL warehouse seeing the same object, so the read path needed a retry budget sized off actual observed timelines, not a guess.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;06&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Alerting that isn’t just “check the app”&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;A validation result nobody sees is worth nothing. Two channels out:&lt;/FONT&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Microsoft Teams&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;webhooks, scoped per channel by trigger — all runs, scheduled-only, or manual-only — so a channel used for production monitoring doesn’t fill up with every dry run an author fires off while iterating.&lt;/FONT&gt;&lt;/LI&gt;&lt;LI&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;STRONG&gt;Site24x7&lt;/STRONG&gt;, or any uptime monitor, via a pair of read-only status endpoints — latest result by table, or by a specific run id — that return a plain 200 on a clean run and a 503 the moment error rows show up. External monitoring infrastructure gets to treat data quality the same way it treats an HTTP health check, without knowing anything about DQX.&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;07&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Schedules that know what they’re sampling&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;“Validate the last hour” only means something if the scheduler knows which column is time, and on a table it’s never seen before, that’s a real guess. It tries typed timestamp/date columns first, then a name-priority list, and surfaces the candidates as editable tags — a schedule owner can pin the right one, remove one that’s wrong for their table, or leave it on automatic. Row Scope pairs with tag-based scope filtering, so one schedule can target “every approved rule labeled&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;pii” across every table that has one, on either the full table or a time-windowed sample, instead of one schedule per table.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;08&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Closing the loop, not just quarantining&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Everything above eventually produces failing rows, and Studio’s side of what happens to them is deliberately small: a per-dataset YAML runbook, edited or uploaded in the app, written straight to a Unity Catalog Volume. The app checks that it’s valid YAML and writes it — it doesn’t parse rule names, strategies, or parameters, so a new remediation strategy is a change to the consuming pipeline, never to Studio.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What reads those runbooks — an agentic re-ingestion system that tries the documented fix first and falls back to an LLM only for what nobody has written a playbook for — is its own build, written up separately:&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;A href="https://keerthimath.github.io/blog/agent-proposes-dqx-disposes/" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Agent Proposes, DQX Disposes →&lt;/STRONG&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;09&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;dqx-validator — the write side of the dead-letter pattern&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Everything so far assumes rows are already in a Silver table by the time DQX sees them.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;dqx-validator&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is the piece that runs earlier — a standalone library an ingestion pipeline calls directly, mid-flight, to apply Studio’s own approved rules to a DataFrame before it lands, and quarantine whatever fails.&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;It loads&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;status = 'approved'&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;checks from the same rules tables Studio’s approval workflow writes to, resolved per-environment from a small table config. The loader is exposed as a single function,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;approved_checks_for(table_fqns), and the agentic re-ingestion side calls the exact same function — so the checks that quarantined a row and the checks that later re-validate a proposed fix are guaranteed to be the same object, not two copies that can drift.&lt;BR /&gt;&lt;BR /&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_3-1789797710611.png" style="width: 708px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31317i40D76A52F8CD8492/image-dimensions/708x209?v=v2" width="708" height="209" role="button" title="KeerthiItigimat_3-1789797710611.png" alt="KeerthiItigimat_3-1789797710611.png" /&gt;&lt;/span&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Splitting the DataFrame is one call to DQX’s own&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;apply_checks_by_metadata_and_split&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;— no custom validation logic, just the public engine. The quarantine row carries the input schema unchanged plus bookkeeping columns (&lt;/SPAN&gt;_error&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_warning&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_generated_at&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_data_source&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_rule_sources&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_is_cross_table&lt;SPAN&gt;), and one more that matters more than it looks:&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_row_id&lt;SPAN&gt;, a hash of whichever business-key columns the caller names. That’s the field the entire review queue on the read side keys every quarantined row on. Name the wrong columns, or none, and the hash falls back to every input column — which still works, but logs a warning, because at that point two rows differing only in a bookkeeping timestamp look like two different violations instead of one.&lt;/SPAN&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;/DIV&gt;&lt;DIV class=""&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeerthiItigimat_2-1789797607909.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/31316i946E6220F74AF4AF/image-size/large?v=v2&amp;amp;px=999" role="button" title="KeerthiItigimat_2-1789797607909.png" alt="KeerthiItigimat_2-1789797607909.png" /&gt;&lt;/span&gt;&lt;/DIV&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#808080"&gt;&lt;STRONG&gt;Figure 2.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Items 08 and 09 are two ends of the same loop — dqx-validator writes to quarantine, the remediation pipeline reads it back out, and both sides resolve rules the same way.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;10&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;Ask it directly, instead of building another chart&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;Studio has an Insights dashboard for the questions we anticipated. It doesn’t have one for the question someone asks once, in passing — “which tables had a spike in errors last Tuesday” isn’t worth a dashboard tile, but it’s a completely reasonable thing to want to know. A Genie space scoped to Studio’s own schema — run history, quarantine records, metrics — turns that into a conversation instead of a feature request: ask in plain language, get an answer grounded in the real tables, no ticket filed to add a chart nobody will look at again.&lt;/FONT&gt;&lt;/P&gt;&lt;H2&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;&lt;SPAN class=""&gt;&lt;FONT size="3"&gt;11&lt;/FONT&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;What all of this has in common&lt;/FONT&gt;&lt;/H2&gt;&lt;P&gt;&lt;FONT face="georgia,palatino" color="#333333"&gt;None of the ten items above touch the DQX library. Every one of them is a caller of its public API, the same way a notebook is — the editor, the validator library, the agentic pipeline, all of it sits beside DQX rather than inside a fork of it. That was never an accident; it’s the only way any of this stays upgradeable as the library keeps moving.&lt;/FONT&gt;&lt;/P&gt;&lt;P class=""&gt;&lt;STRONG&gt;&lt;FONT face="georgia,palatino" size="4" color="#008080"&gt;Studio is documented. This is everything the documentation doesn’t say yet.&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;</description>
      <pubDate>Sat, 19 Sep 2026 06:04:43 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/dqx-everything-the-docs-don-t-say-yet/m-p/169166#M1580</guid>
      <dc:creator>KeerthiItigimat</dc:creator>
      <dc:date>2026-09-19T06:04:43Z</dc:date>
    </item>
  </channel>
</rss>

