Approach: Use a single transactional MERGE (atomic in modern warehouses) to detect inserts, updates (changes to attributes) and soft-close old records for SCD Type 2. Use the MERGE ... OUTPUT (or RETURNING) clause to capture row-level events and write audit entries. Wrap in a transaction and assume the warehouse provides snapshot isolation for consistent MERGE semantics (or serializable for stronger guarantees). If concurrent writers exist, use a deduped staging key and rely on the MERGE being atomic per-transaction.SQL (T-SQL / Synapse / SQL Server style — adapt RETURNING syntax for other warehouses):sql
BEGIN TRANSACTION;
DECLARE @now datetime2 = SYSUTCDATETIME();
-- Temporary table to collect audit rows from OUTPUT
CREATE TABLE #audit_tmp (
order_id bigint,
change_type varchar(10),
changed_at datetime2
);
MERGE INTO orders_dim AS target
USING (
SELECT * FROM staging_orders -- deduped and latest per order_id
) AS src
ON target.order_id = src.order_id AND target.is_current = 1
WHEN MATCHED AND (
ISNULL(target.customer_id,0) <> ISNULL(src.customer_id,0)
OR ISNULL(target.status,'') <> ISNULL(src.status,'')
OR ISNULL(target.amount,0) <> ISNULL(src.amount,0)
) THEN
-- Close current version
UPDATE SET
target.is_current = 0,
target.effective_to = @now
OUTPUT src.order_id, 'UPDATE' , @now INTO #audit_tmp;
WHEN NOT MATCHED BY TARGET THEN
-- New order: insert current row
INSERT (order_id, customer_id, status, amount, effective_from, effective_to, is_current)
VALUES (src.order_id, src.customer_id, src.status, src.amount, @now, NULL, 1)
OUTPUT src.order_id, 'INSERT', @now INTO #audit_tmp;
-- After closing the old current row, insert the new current version for updates
-- Do this by inserting rows from staging for those that matched and changed.
;WITH changed AS (
SELECT s.*
FROM staging_orders s
JOIN #audit_tmp a ON a.order_id = s.order_id AND a.change_type = 'UPDATE'
)
INSERT INTO orders_dim (order_id, customer_id, status, amount, effective_from, effective_to, is_current)
SELECT order_id, customer_id, status, amount, @now, NULL, 1
FROM changed;
-- Persist audit entries
INSERT INTO orders_audit (order_id, change_type, changed_at)
SELECT order_id, change_type, changed_at FROM #audit_tmp;
DROP TABLE #audit_tmp;
COMMIT TRANSACTION;
Key points / assumptions:- MERGE + OUTPUT used to capture row-level events in the same transaction so audit is consistent with dimensional changes.- Assumes warehouse MERGE is atomic and uses snapshot isolation (or serializable) so concurrent MERGEs won't interleave and create overlapping current rows. If only read-committed is available, consider table-level locking or a serializable transaction.- staging_orders should be pre-deduped (one row per order_id) to avoid ambiguity.Edge cases:- No-op rows (no attribute changes) are ignored.- If multiple attributes change, a single SCDv2 cycle closes old row and inserts new row once.- For very large volumes, consider batching by time window to keep transaction size manageable.