How Data Diff Checks Work
Definition
Asserts that two datasets match on a chosen set of fields. The check compares a target container (the dataset the check is attached to) against a reference container in another datastore, and reports every row that is added, removed, or changed between the two sides.
Recommended Check
Qualytics recommends using the Data Diff rule (dataDiff) instead of the deprecated Is Replica Of rule (isReplicaOf).
Both rules share the same row-by-row comparison engine and the same configuration properties (Row Identifiers, Passthrough Fields, and per-type Comparators). The differences:
- Only Data Diff is actively maintained.
- Only Data Diff supports the
diff_change_typesproperty, which restricts anomalies to a chosen subset of statuses (added,removed,changed). See How It Works for details.
Overview
Data Diff is a two-table comparison rule. Use it whenever you need to confirm that one dataset is an exact copy, or a controlled copy, of another:
- Validating that a replica, backup, or warehouse mirror matches its source of truth.
- Comparing pre- and post-migration data after a system move.
- Verifying that a derived table, such as an aggregate, snapshot, or reporting view, still agrees with the upstream system.
- Confirming that an exported file delivered to a partner contains the same rows as the system of record.
Beyond the standard properties, a Data Diff check has three configuration inputs: the reference datastore and container to compare against, an optional list of Row Identifiers (the key the platform uses to match each target row to its reference row), and an optional set of Comparators (per-field tolerances for numeric, duration, and string fields).
When Row Identifiers are set, anomalies appear in the Comparison Source Records view, which shows each differing row side by side as Left (target) vs Right (reference) values.
Field Scope
Multiple: The check evaluates one or more fields by comparing them between target and reference.
Accepted Types
| Type | Supported |
|---|---|
Date |
|
Timestamp |
|
Integral |
|
Fractional |
|
String |
|
Boolean |
General Properties
| Name | Supported |
|---|---|
Filter Allows the targeting of specific data based on conditions |
|
Coverage Customization Allows adjusting the percentage of records that must meet the rule's conditions |
The filter allows you to define a subset of data upon which the rule will operate.
It requires a valid Spark SQL expression that determines the criteria rows in the DataFrame should meet. This means the expression specifies which rows the DataFrame should include based on those criteria. Since it's applied directly to the Spark DataFrame, traditional SQL constructs like WHERE clauses are not supported.
Examples
Direct Conditions
Simply specify the condition you want to be met.
Combining Conditions
Combine multiple conditions using logical operators like AND and OR.
Correct usage" collapsible="true
Incorrect usage" collapsible="true
Utilizing Functions
Leverage Spark SQL functions to refine and enhance your conditions.
Correct usage" collapsible="true
Incorrect usage" collapsible="true
Using scan-time variables
To refer to the current dataframe being analyzed, use the reserved dynamic variable {{_qualytics_self}}.
Correct usage" collapsible="true
Incorrect usage" collapsible="true
While subqueries can be useful, their application within filters in our context has limitations. For example, directly referencing other containers or the broader target container in such subqueries is not supported. Attempting to do so will result in an error.
Important Note on {{_qualytics_self}}
The {{_qualytics_self}} keyword refers to the dataframe that's currently under examination. In the context of a full scan, this variable represents the entire target container. However, during incremental scans, it only reflects a subset of the target container, capturing just the incremental data. It's crucial to recognize that in such scenarios, using {{_qualytics_self}} may not encompass all entries from the target container.
Anomaly Types
| Type | Supported |
|---|---|
| Record Flag inconsistencies at the row level |
|
| Shape Flag inconsistencies in the overall patterns and distributions of a field |
How the Check Evaluates the Two Datasets
Every Data Diff check follows the same five-step evaluation flow:
- Apply the filter clauses. If the check has a
filterset, only the rows in the target container that match it continue to the next step. The reference side has its own Filter Clause (properties.ref_filter); when it is set, only the reference rows matching it are read. - Read the reference container. The platform reads the comparison fields from the reference table or file in the configured reference datastore, scoped by
ref_filterwhen one is set. - Match rows. With Row Identifiers configured, rows are matched by the combination of identifier values: target rows whose identifier values match a reference row are paired up. Without Row Identifiers, the platform performs a symmetrical set difference on the full set of compared fields.
- Compare fields on each matched pair. For every paired row, each listed field is compared between left (target) and right (reference). Comparators (numeric, duration, string) define the tolerance for the comparison; without a Comparator the comparison is strict equality.
- Emit a single Shape Anomaly summarizing the diffs. Every row that differs (added, removed, or changed) becomes part of the anomaly's source records. The dataset-level violation is summarized in a single Shape Anomaly message and the per-row detail is surfaced through the Comparison Source Records view.
The order matters: the filter is applied before matching, so rows the filter excludes on the target side cannot pair with any reference row and cannot contribute to the anomaly.
The Three Diff Statuses
Each differing row carries one of three status values. The status is what drives the Comparison Source Records UI and is exposed in the anomaly payload.
| Status | Meaning |
|---|---|
added |
The row's identifier exists only on the left (target). The target has a row the reference does not. |
removed |
The row's identifier exists only on the right (reference). The target is missing a row the reference has. |
changed |
The same identifier exists on both sides, but at least one listed field has a different value. The differing fields are reported per row. |
The statuses are named from the target's point of view: the target is the dataset under check, so a row only it has is one it added, and a row only the reference has is one it removed.
Without Row Identifiers, only added and removed are produced, because the platform has no key to match rows on. A row that differs in even one field becomes one added (the row from target) and one removed (the row from reference), in a pure symmetrical set difference.
Row Identifiers matter for clarity
Setting Row Identifiers turns most discrepancies into a single changed row rather than a pair of added/removed rows. This is the only way to get a per-field diff (left vs right on the same row) in the Comparison Source Records view. Pick Row Identifiers that uniquely identify each row in both target and reference.
Restricting Anomalies by Status
The diff_change_types property restricts which of the three diff statuses are allowed to produce an anomaly. By default, every differing row (added, removed, or changed) contributes to the anomaly count. Setting diff_change_types to a subset of statuses keeps the comparison running over all rows but only flags the diffs whose status appears in the list.
Value omitted or null |
Behavior |
|---|---|
Property absent from properties |
All three statuses (added, removed, changed) fire anomalies. |
["added"] |
Only rows present on the target side but missing on the reference are flagged. |
["removed"] |
Only rows present on the reference side but missing on the target are flagged. |
["changed"] |
Only rows present on both sides whose listed fields differ are flagged. |
| Any combination of the three values | Only the listed statuses fire. |
[] (empty list) |
Rejected at the API with HTTP 422; at least one status must be selected. |
Typical use cases:
- Reference is intentionally a superset of the target. The target is a production table whose reference (a staging or canonical source) carries extra QA or onboarding rows that production is not expected to receive. Those extra reference rows arrive as
removed, so setdiff_change_typesto["added", "changed"]to suppress them. - Reference is intentionally a subset of the target. A reporting view may exclude soft-deleted or out-of-scope rows. The extra target rows arrive as
added, so setdiff_change_typesto["removed", "changed"]to suppress them. - Only the per-field diff matters. When row presence on both sides is guaranteed by an upstream contract and only value drift is interesting, set
diff_change_typesto["changed"].
The property is dataDiff-only. Sending it on an isReplicaOf check is rejected at the API with HTTP 422. The property is editable through PUT /api/quality-checks/{id}, so the subset can be tuned without recreating the check.
Row Identifiers and Passthrough Fields
Two optional properties shape what the Comparison Source Records view looks like:
Row Identifiers
A list of fields that form the compound key the platform uses to pair target and reference rows. The identifier tuple must exist on both sides. Typical choices are primary keys (customer_id, order_id) or a composite of business-key columns (order_id, line_number).
When Row Identifiers are set:
- Matched rows produce a
changeddiff if any listed field differs. - Unmatched target rows produce
added. - Unmatched reference rows produce
removed.
Passthrough Fields
Extra fields the platform should carry into the source-records output for context, even though they are not part of the comparison. Passthrough Fields appear in the Comparison Source Records view alongside the diffed fields but are never themselves a reason for the anomaly to fire. Typical use: showing customer_name or created_at next to the differing column so anomaly triagers can identify the row without leaving the page.
The Filter Clauses
Data Diff has two independent filter clauses, both optional and both SQL WHERE expressions the platform evaluates with its own SQL syntax, which stays the same whatever the underlying datastore is:
- The check's Filter Clause scopes the target container before matching.
- The Filter Clause in the Right Reference panel (
properties.ref_filter) scopes the reference container.
A side whose clause is left empty is read in full.
Filtering the target side serves two purposes:
- Scoping the comparison. Restrict the comparison to a subset of target rows (
status = 'active',event_date = current_date(),tenant_id = 42). Rows outside the scope cannot be reported asaddedand cannot pair with reference rows. - Avoiding noise from known divergences. Filter out rows that are intentionally allowed to differ between target and reference (for example, a
staging_only = trueflag), so the check focuses on the rows that must match.
Both clauses are part of the check definition. Unlike the row-level rule types, Data Diff does not echo them in the anomaly message, so read the check itself to see which slice of each side was evaluated.
Scope both sides deliberately
The check's Filter Clause narrows the target only. To narrow the reference as well (for example, comparing only this month's records on both sides), set the Filter Clause in the Right Reference panel; it accepts Check Variables. Leaving the two clauses out of sync compares different slices, which surfaces as added and removed rows that are really just scope mismatches.
Comparators
Comparators apply a per-field tolerance to the equality check between left and right values. Without a Comparator, the platform compares values strictly: 1.00 and 1.000001 differ, "Australia" and "australia" differ.
The Comparators allow you to set margins of error, accommodating slight variations in data validation. This flexibility is crucial for maintaining data integrity, especially when working with different data types such as numeric values, durations, and strings. Here's an overview of how each type of comparator can be beneficial for you:
Numeric
Numeric comparators enable you to compare numbers with a specified margin, which can be a fixed absolute value or a percentage. This allows for minor numerical differences that are often acceptable in real-world data.
Comparison Type
- Absolute Value: Uses a fixed threshold for determining equality. It's ideal when you need consistent precision across measurements.
- Percentage Value: Uses a percentage of the original value as the threshold for equality comparisons. It's suitable for floating point numbers where precision varies.
Threshold
The threshold is the value you set to define the margin of error:
- When using Absolute Value, the threshold represents the maximum allowable difference between two values for them to be considered equal.
- For Percentage Value, the threshold is the percentage that describes how much a value can deviate from a reference value and still be considered equal.
Illustration using Absolute Value
In this example, it compares Value A and Value B according to the defined Threshold of 50.
| Value A | Value B | Difference | Are equal? |
|---|---|---|---|
| 100 | 150 | 50 | True |
| 100 | 90 | 10 | True |
| 100 | 155 | 55 | False |
| 100 | 49 | 51 | False |
Illustration using Percentage Value
In this example, it compares Value A and Value B according to the defined Threshold of 10%.
Percentage Change Formula: [ (Value B - Value A) / Value A ] * 100
| Value A | Value B | Percentage Change | Are equal? |
|---|---|---|---|
| 120 | 132 | 10% | True |
| 150 | 135 | 10% | True |
| 200 | 180 | 10% | True |
| 160 | 150 | 6.25% | True |
| 180 | 200 | 11.11% | False |
Duration
Duration comparators support time-based comparisons, allowing for flexibility in how duration differences are managed. This flexibility is crucial for datasets where time measurements are essential but can vary slightly.
Unit
The unit of time you select determines how granular the comparison is:
- Millis: Measures time in milliseconds, ideal for high-precision needs.
- Seconds: Suitable for most general purposes where precision is important but doesn't need to be to the millisecond.
- Days: Best for longer durations.
Value
Value sets the maximum acceptable difference in time to consider two values as equal. It serves to define the margin of error, accommodating small discrepancies that naturally occur over time.
Illustration using Duration Comparator
| Unit | Value A | Value B | Difference | Threshold | Are equal? |
|---|---|---|---|---|---|
| Millis | 500 ms | 520 ms | 20 ms | 25 ms | True |
| Seconds | 30 sec | 31 sec | 1 sec | 2 sec | True |
| Days | 5 days | 7 days | 2 days | 1 day | False |
| Millis | 1000 ms | 1040 ms | 40 ms | 25 ms | False |
| Seconds | 45 sec | 48 sec | 3 sec | 2 sec | False |
String
String comparators facilitate comparisons of textual data by allowing variations in spacing. This capability is essential for ensuring data consistency, particularly where minor text inconsistencies may occur.
Ignore Whitespace
When enabled, this setting allows the comparator to ignore differences in whitespace. This means sequences of whitespace are collapsed into a single space, and any leading or trailing spaces are removed. This can be particularly useful in environments where data entry may vary in formatting but where those differences are not relevant to the data's integrity.
Illustration
In this example, it compares Value A and Value B according to the defined string comparison to ignore whitespace as True.
| Value A | Value B | Are equal? | Has whitespace? |
|---|---|---|---|
Leonidas |
Leonidas |
True | No |
Beth |
Beth |
True | Yes |
Ana |
Anna |
False |
Yes |
Joe |
Joel |
False |
No |
See Also
-
Anomaly Reporting
The anomaly message the check produces, what the counts mean, and how differing rows appear side by side.
-
Examples
Three production scenarios with sample data, anomaly messages, and the resulting comparison view.
-
Best Practices
Guidelines for choosing Row Identifiers, tuning Comparators, scoping both sides, and keeping the cost down.
-
Permissions
The team permission each action needs: view, create, edit, archive, restore, and delete.