Skip to content

Fix/ma sat v0 inserting non deltas on incremental runs - #486

Open
pkumar-data wants to merge 14 commits into
mainfrom
fix/ma_sat_v0_inserting_non_deltas_on_incremental_runs
Open

Fix/ma sat v0 inserting non deltas on incremental runs#486
pkumar-data wants to merge 14 commits into
mainfrom
fix/ma_sat_v0_inserting_non_deltas_on_incremental_runs

Conversation

@pkumar-data

Copy link
Copy Markdown
Contributor

Description

Title: fix: ma_sat_v0 incremental delta logic - all adapters including SQL Server & Trino

Body:

Summary

Fixes the ma_sat_v0 incremental delta failure affecting all database adapters:

  • Re-insertion of non-deltas (loading same data multiple times)
  • Skipping valid deltas (missing changes when values revert within batch)

Root Cause & Solution

Problem: PARTITION BY with |lower caused casing issues; batch reverts weren't detected

Solution:

  1. Removed identifier casing manipulation (|lower)
  2. Added ROW_NUMBER() as rn to track changes within batches
  3. Only compare rn=1 rows against satellite history; keep rn>1 automatically

Adapters Covered

✅ BigQuery, Databricks, Exasol, Fabric, Oracle, Postgres, Redshift, Snowflake, Synapse
✅ SQL Server (newly included)
✅ Trino (newly included)

Status

Branch has been rebased/merged with main to include SQL Server and Trino implementations.
Ready for merge to main.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the ma_sat_v0 incremental delta logic across multiple adapter-specific implementations to prevent re-inserting non-deltas and to correctly retain intra-batch changes (including “revert within batch” scenarios). It does so by removing identifier casing manipulation in window partitions and introducing an rn (row number) to distinguish the first change in a batch from subsequent changes.

Changes:

  • Removed |lower casing manipulation from PARTITION BY in “latest in sat” lookups.
  • Added ROW_NUMBER() (rn) into the source dedupe/change-tracking pipeline.
  • Updated incremental records_to_insert filtering to always keep rn > 1 rows while only comparing the first row per key to existing satellite history.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
macros/tables/bigquery/ma_sat_v0.sql Removes `
macros/tables/databricks/ma_sat_v0.sql Removes `
macros/tables/exasol/ma_sat_v0.sql Removes `
macros/tables/fabric/ma_sat_v0.sql Adds rn and changes incremental insertion predicate logic (Fabric already escapes identifiers).
macros/tables/oracle/ma_sat_v0.sql Removes `
macros/tables/postgres/ma_sat_v0.sql Removes `
macros/tables/redshift/ma_sat_v0.sql Adds rn and changes incremental insertion predicate logic.
macros/tables/snowflake/ma_sat_v0.sql Removes `
macros/tables/synapse/ma_sat_v0.sql Removes `

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread macros/tables/bigquery/ma_sat_v0.sql Outdated
Comment thread macros/tables/snowflake/ma_sat_v0.sql Outdated
Comment thread macros/tables/synapse/ma_sat_v0.sql Outdated
Comment thread macros/tables/postgres/ma_sat_v0.sql Outdated
Comment thread macros/tables/redshift/ma_sat_v0.sql Outdated
Comment thread macros/tables/oracle/ma_sat_v0.sql Outdated
Comment thread macros/tables/fabric/ma_sat_v0.sql Outdated
Comment thread macros/tables/exasol/ma_sat_v0.sql Outdated
Comment thread macros/tables/databricks/ma_sat_v0.sql Outdated

@tkiehn tkiehn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally to the Review from Copilot:

✅ SQL Server (newly included)
✅ Trino (newly included)

Are these adapter already following the new logic? I don't seem them included in this PR.

@tkiehn

tkiehn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Companion regression test: ScalefreeCOM/datavault4dbt-automatic-tests#240

It adds a dedicated multi-batch fixture (ma_sat_v0_05) reproducing the exact scenario this PR fixes — within one incremental load a multi-active group changes and then reverts to the sat's current latest hashdiff (A → B → A across three ascending ldts). Assertions: rsrc='revert' → 2 rows after init, 6 after the delta run (the pre-fix macro drops the reverting group and yields 4); plus a non-delta rsrc='nochange' over-insertion guard (1/1).

Note it exercises the default multi-batch path (source_is_single_batch/disable_hwm left at defaults), which is where the rn logic lives. Two points from reviewing this PR that the test surfaces:

  1. Single-batch + incremental compiles to an invalid reference. records_to_insert hardcodes WHERE deduped_rows.rn > 1 OR ..., but deduped_rows only exists under {% if not source_is_single_batch %}. With source_is_single_batch=true the CTE is absent and source_cte stays source_data, so the incremental run fails (deduped_rows not found). Suggest guarding it, e.g. WHERE {% if not source_is_single_batch %}{{ source_cte }}.rn > 1 OR {% endif %}NOT EXISTS (...). This is already exercised by ma_sat_v0_03 / ma_sat_v0_04 (both source_is_single_batch: true + incremental).
  2. SQL Server and Trino are not actually changed by this PR despite the description marking them ✅ — macros/tables/sqlserver/ma_sat_v0.sql and macros/tables/trino/ma_sat_v0.sql still carry the old WHERE NOT EXISTS (...) with no rn. Either apply the same fix there or adjust the description.

…r/Trino adapters

- Wrap deduped_rows.rn > 1 in if not source_is_single_batch across
  all 9 adapters (bigquery, snowflake, postgres, databricks, exasol, fabric,
  oracle, redshift, synapse) to prevent invalid CTE reference when
  source_is_single_batch=true
- Add ROW_NUMBER() rn column to SQL Server and Trino lag_source_data,
  deduped_row_hashdiff, and deduped_rows CTEs to bring them in line with
  the other adapters
- Apply the same guarded WHERE clause to SQL Server and Trino records_to_insert

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@tkiehn tkiehn added the waiting-on-maintainers Waiting for a response from the maintainers. label Jul 13, 2026
@tkiehn

tkiehn commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

…apters

Window functions in latest_entries_in_sat and deduped_row_hashdiff were
partitioned by parent_hashkey only, causing all MA key rows in the same
hashkey group to share a single rn sequence. Rows with rn>1 were always
inserted, bypassing the NOT EXISTS delta check. Fixed by adding
src_ma_key_list to every PARTITION BY so each MA key gets its own
independent row sequence.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 17 comments.

Comment thread macros/tables/bigquery/ma_sat_v0.sql Outdated
Comment thread macros/tables/snowflake/ma_sat_v0.sql Outdated
Comment thread macros/tables/redshift/ma_sat_v0.sql Outdated
Comment thread macros/tables/exasol/ma_sat_v0.sql
Comment thread macros/tables/databricks/ma_sat_v0.sql Outdated
Comment thread macros/tables/postgres/ma_sat_v0.sql Outdated
Comment thread macros/tables/oracle/ma_sat_v0.sql
Comment thread macros/tables/oracle/ma_sat_v0.sql Outdated
Comment thread macros/tables/fabric/ma_sat_v0.sql
Comment thread macros/tables/fabric/ma_sat_v0.sql Outdated
…in condition

deduped_row_hashdiff (and lag_source_data for subquery adapters) were
partitioned by MA key but did not project those columns into the CTE
output. The join back to source_data on (hashkey, ldts, hashdiff) alone
could multiply rows or assign the wrong rn when multiple MA key rows
share the same ldts and hashdiff. MA key columns are now selected in
deduped_row_hashdiff and included in the join condition across all 11
adapters.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.

Comments suppressed due to low confidence (1)

macros/tables/synapse/ma_sat_v0.sql:140

  • The incremental NOT EXISTS check only compares parent_hashkey + hashdiff, but latest_entries_in_sat is now deduped per (parent_hashkey, src_ma_key). If two different multi-active keys share the same hashdiff, this can incorrectly suppress inserts for one key. Include the MA key column(s) in latest_entries_in_sat and add a multikey(src_ma_key, ...) predicate to this NOT EXISTS filter (and ensure the MA key columns are selected in the latest_entries CTE).
            AND {{ datavault4dbt.multikey(ns.hdiff_alias, prefix=['latest_entries_in_sat', source_cte], condition='=') }}
            )
    {%- endif %}

    )

Comment thread macros/tables/trino/ma_sat_v0.sql
Comment thread macros/tables/sqlserver/ma_sat_v0.sql
Comment thread macros/tables/snowflake/ma_sat_v0.sql
Comment thread macros/tables/redshift/ma_sat_v0.sql
Comment thread macros/tables/postgres/ma_sat_v0.sql
Comment thread macros/tables/oracle/ma_sat_v0.sql
Comment thread macros/tables/exasol/ma_sat_v0.sql
Comment thread macros/tables/databricks/ma_sat_v0.sql
Comment thread macros/tables/fabric/ma_sat_v0.sql
Comment thread macros/tables/bigquery/ma_sat_v0.sql
…S check

Add MA key column(s) to latest_entries_in_sat SELECT (and latest_entries_in_sat_prep
for subquery-based adapters) so the NOT EXISTS deduplication is scoped per
(hashkey, MA key) rather than just per hashkey. Add multikey(src_ma_key, ...)
predicate to the NOT EXISTS WHERE clause across all 11 adapters, preventing
false suppression when two distinct MA keys share the same hashdiff.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@tkiehn

tkiehn commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

@tkiehn tkiehn added waiting-on-response Waiting on a response from the author or others, except maintainers. and removed waiting-on-maintainers Waiting for a response from the maintainers. labels Jul 17, 2026
…s filter

Ghost records ("unknown"/"error" defaults) carry a NULL multi-active key.
Since NULL = NULL evaluates to unknown in SQL, the plain equality checks
in multikey() never matched an already-loaded ghost row against the
NOT EXISTS dedup check or the deduped_rows join, causing them to be
re-inserted as duplicates on every incremental run (or silently dropped,
depending on which code path a model uses).

Adds an opt-in null_safe flag to multikey() and applies it to the two
MA key comparisons in ma_sat_v0, across all 11 adapters. Also removes a
BigQuery-only stray filter in latest_entries_in_sat that excluded rows
with ldts = end_of_all_times, which permanently hid the "error" ghost
record from the dedup lookup regardless of the null-safety fix.

Verified against BigQuery and SQL Server: reproduced the original
failure on both by reverting the fix, confirmed the fix resolves it
across full-refresh, incremental, and repeat-incremental runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pkumar-data

Copy link
Copy Markdown
Contributor Author

CI kept failing on ma_sat_v0_03's test with a duplicate-row error. It looked like it might be about business-key deltas being re-inserted, but reproducing it against for example in BigQuery and inspecting the actual duplicate rows showed the culprit was auto-generated ghost records (the "unknown" and "error" placeholder rows every staging model emits) not real data.

Solve:
Added an opt-in null_safe parameter to the shared multikey macro (defaults to false, so its ~150 other call sites elsewhere in the package are unaffected) and used it at the two MA-key comparison points inside ma_sat_v0, identically across all 11 adapters.

Ran the test again locally and all passed.

@pkumar-data pkumar-data added waiting-on-maintainers Waiting for a response from the maintainers. and removed waiting-on-response Waiting on a response from the author or others, except maintainers. labels Jul 17, 2026
@remoteworkflow

Copy link
Copy Markdown

dbt test combined result: ❌


Details

RESULTS for Synapse:
❌ dbt-core-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Postgres:
✅ dbt-core-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for BigQuery:
✅ dbt-core-tests
✅ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Redshift:
✅ dbt-core-tests
✅ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Snowflake:
✅ dbt-core-tests
✅ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Exasol:
❌ dbt-core-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Fabric:
❌ dbt-core-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Oracle:
✅ dbt-core-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Databricks:
⚠️ dbt-core-tests
❌ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for SQL Server:
✅ dbt-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


RESULTS for Trino:
❌ dbt-tests
⚠️ dbt-fusion-tests
⚠️ dbt-macro-tests
⚠️ tech-tests


Link to workflow summary: https://github.com/ScalefreeCOM/datavault4dbt-ci-cd/actions/runs/29575568515

@pkumar-data

Copy link
Copy Markdown
Contributor Author

The ma_sat_v0 non-delta insertion bug has been fixed across all 11 adapters (BigQuery/default, Snowflake, Redshift, Exasol, Databricks, Postgres, Oracle, Fabric, SQL Server, Synapse, Trino).

Root cause: Window functions and the latest_entries_in_sat lookup were partitioned by parent_hashkey only — all MA keys for the same hashkey were grouped together. This caused rows with rn > 1 to always be inserted, and the NOT EXISTS check could suppress valid inserts when two MA keys shared the same hashdiff.

Fix applied (3 commits):

Partition all window functions by (parent_hashkey, MA key) so each MA key deduplicates independently
Project MA key in deduped_row_hashdiff SELECT and include it in the deduped_rows JOIN condition
Project MA key in latest_entries_in_sat SELECT and add multikey(src_ma_key, …) to the NOT EXISTS WHERE predicate — history lookup is now scoped per (hashkey, MA key)
Re: CI failures for example in Trino failure is an S3/Iceberg infrastructure error on an nh_link test (unrelated to this fix); the Databricks run was aborted because the warehouse stopped after 1h15m (resource timeout, not a code issue). Neither is a regression from these changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-maintainers Waiting for a response from the maintainers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants