<?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>article From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/ba-p/155355</link>
    <description>&lt;P&gt;&lt;SPAN&gt;Every data warehouse has the same foundational problem: keeping dimension tables in sync with operational systems. Customer records change. Orders get updated. Accounts get closed. Getting those changes into your warehouse -- correctly, reliably, and in the right order -- is the most critical pipeline pattern in any data platform. And yet, the tool most teams reach for -- MERGE INTO -- was never designed for this job. It does not understand event ordering, deduplication, or delete propagation. Engineers build all of that themselves, and what starts as "just merge the new records" spirals into 150+ lines of procedural SQL. Every team reimplements this pattern. Some get it subtly wrong. The failures are silent -- you find out weeks later when a dashboard is wrong or an audit fails.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/ldp/dbsql/streaming#apply-change-data-capture-cdc-with-auto-cdc-flows" target="_self"&gt;AUTO CDC&lt;/A&gt; is a new declarative SQL API in &lt;A href="https://docs.databricks.com/aws/en/ldp/dbsql/dbsql-for-ldp" target="_self"&gt;Databricks SQL&lt;/A&gt; that handles all of this for you. You declare your keys, your ordering column, and your SCD type -- and AUTO CDC handles dedup, ordering, deletes, checkpointing, and history tracking automatically. What used to take 150+ lines of fragile MERGE logic becomes about 7 lines of SQL. AUTO CDC is available now in Beta -- you can try it today in the Databricks SQL Query Editor, running directly on your SQL warehouse.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2026-04-23 at 17.23.21.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/26365i6C621CB1E6953CC0/image-size/large?v=v2&amp;amp;px=999" role="button" title="Screenshot 2026-04-23 at 17.23.21.png" alt="Screenshot 2026-04-23 at 17.23.21.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt; &lt;SPAN&gt;In this post, we will walk through the pain points that every warehouse engineer knows too well, show how AUTO CDC eliminates them, and build a working SCD Type 2 pipeline from scratch.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;The Challenge: MERGE INTO creates Technical Debt&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;If you have built &lt;A href="https://docs.databricks.com/aws/en/ldp/what-is-change-data-capture" target="_self"&gt;Change Data Capture&lt;/A&gt; (CDC) pipelines with MERGE INTO, these scenarios will feel familiar:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Multiple updates to the same key in one batch&lt;/STRONG&gt;&lt;SPAN&gt; -- Your source sends two updates for &lt;/SPAN&gt;&lt;SPAN&gt;customer_id = 42&lt;/SPAN&gt;&lt;SPAN&gt; in the same load. Which one wins? Without explicit dedup logic, the answer is "whichever the query optimizer picks" -- and that is not deterministic.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Out-of-order records across batches&lt;/STRONG&gt;&lt;SPAN&gt; -- A record with &lt;/SPAN&gt;&lt;SPAN&gt;updated_at = 10:05 AM&lt;/SPAN&gt;&lt;SPAN&gt; arrives in batch 3, but the record with &lt;/SPAN&gt;&lt;SPAN&gt;updated_at = 10:02 AM&lt;/SPAN&gt;&lt;SPAN&gt; arrives in batch 4. Your MERGE happily overwrites the newer data with the older data. Silently.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Delete handling&lt;/STRONG&gt;&lt;SPAN&gt; -- You process a delete for &lt;/SPAN&gt;&lt;SPAN&gt;customer_id = 99&lt;/SPAN&gt;&lt;SPAN&gt;, then a stale update for the same customer arrives in the next batch. Your MERGE re-inserts the record. The customer is back from the dead.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;SCD Type 2 on top of all that&lt;/STRONG&gt;&lt;SPAN&gt; -- Now layer in historical tracking. You need checksums to detect real changes, CTEs to stage the merge, &lt;/SPAN&gt;&lt;SPAN&gt;LAG&lt;/SPAN&gt;&lt;SPAN&gt;/&lt;/SPAN&gt;&lt;SPAN&gt;LEAD&lt;/SPAN&gt;&lt;SPAN&gt; window functions to compute effective dates, and a &lt;/SPAN&gt;&lt;SPAN&gt;MERGE&lt;/SPAN&gt;&lt;SPAN&gt; statement that handles matched-updates, matched-closes, and not-matched-inserts all at once.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Error recovery&lt;/STRONG&gt;&lt;SPAN&gt; -- Something fails mid-pipeline. The recovery plan? Truncate the target table and rerun from the beginning. Hope it finishes this time.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Here is a simplified glimpse of what that procedural approach looks like, and this is the short version!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Procedural SCD Type 2: The "short" version (~30 lines, real-world is 150+)&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Step 1: Stage incoming data, dedup by key, keep latest&lt;/STRONG&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REPLACE TEMP VIEW staged AS
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (
    PARTITION BY customer_id ORDER BY updated_at DESC
  ) AS rn
  FROM customer_updates
) WHERE rn = 1;
&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;SPAN&gt;Step 2: Compute checksums to detect real changes&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REPLACE TEMP VIEW changes AS
SELECT s.*, md5(concat_ws('|', s.name, s.email, s.city)) AS new_hash
FROM staged s
JOIN customer_dim d ON s.customer_id = d.customer_id AND d.is_current = true
WHERE md5(concat_ws('|', d.name, d.email, d.city)) != md5(concat_ws('|', s.name, s.email, s.city));
&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;STRONG&gt;Step 3: Close existing records + insert new versions&lt;/STRONG&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;MERGE INTO customer_dim AS target
USING (
  SELECT customer_id, name, email, city, updated_at,
         true AS is_current, updated_at AS effective_from, NULL AS effective_to
  FROM changes
  UNION ALL
  SELECT d.customer_id, d.name, d.email, d.city, d.updated_at,
         false, d.effective_from, c.updated_at
  FROM customer_dim d JOIN changes c ON d.customer_id = c.customer_id AND d.is_current = true
) AS source
ON target.customer_id = source.customer_id AND target.is_current = source.is_current
WHEN MATCHED AND source.is_current = false THEN UPDATE SET
  is_current = false, effective_to = source.effective_to
WHEN NOT MATCHED THEN INSERT *;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;And this still does not handle out-of-order records, deletes, or error recovery. Every one of those requires more code, more edge cases, and more testing.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;How AUTO CDC Works&lt;/STRONG&gt;&lt;/H3&gt;
&lt;P&gt;Instead of writing procedural logic to handle every edge case, you declare what you want and &lt;A href="https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-streaming-table-auto-cdc" target="_self"&gt;AUTO CDC&lt;/A&gt; figures out how:&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Out-of-order records&lt;/STRONG&gt;&lt;SPAN&gt; -- &lt;/SPAN&gt;&lt;SPAN&gt;SEQUENCE BY&lt;/SPAN&gt;&lt;SPAN&gt; ensures records are processed in the correct order, regardless of when they arrive. Late-arriving records are slotted into the right position automatically.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Delete propagation&lt;/STRONG&gt;&lt;SPAN&gt; -- &lt;/SPAN&gt;&lt;SPAN&gt;APPLY AS DELETE WHEN&lt;/SPAN&gt;&lt;SPAN&gt; lets you declaratively specify which records represent deletes. No more ghost records.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;SCD Type 1 &amp;amp; Type 2&lt;/STRONG&gt;&lt;SPAN&gt; -- A single keyword: &lt;/SPAN&gt;&lt;SPAN&gt;STORED AS SCD TYPE 1&lt;/SPAN&gt;&lt;SPAN&gt; keeps only the latest version. &lt;/SPAN&gt;&lt;SPAN&gt;STORED AS SCD TYPE 2&lt;/SPAN&gt;&lt;SPAN&gt; maintains full history with &lt;/SPAN&gt;&lt;SPAN&gt;__START_AT&lt;/SPAN&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT&lt;/SPAN&gt;&lt;SPAN&gt; columns -- no checksums, no window functions, no CTEs.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Partial updates -- IGNORE NULL UPDATES prevents null values in sparse CDC records from overwriting existing data -- a common headache with Debezium and other CDC connectors that send partial rows.&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Selective history tracking -- TRACK HISTORY ON lets you control which columns trigger a new SCD Type 2 history row. Changes to untracked columns (e.g., last_login_at) update the current row in place instead of creating unnecessary version bloat.&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Error recovery&lt;/STRONG&gt;&lt;SPAN&gt; -- Built-in checkpointing means the pipeline picks up exactly where it left off. No truncate-and-rerun.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Here is how those pain points map across approaches:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Pain Point&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Procedural MERGE&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;AUTO CDC&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Dedup / multiple updates per key&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;ROW_NUMBER() + manual dedup CTE&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Handled automatically&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Out-of-order records&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Complex CASE logic or ignored entirely&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;SEQUENCE BY&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Delete handling&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Manual MERGE with delete flags&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;APPLY AS DELETE WHEN&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;SCD Type 2 history&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Checksums + LAG/LEAD + multi-arm MERGE&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;STORED AS SCD TYPE 2&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Error recovery&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Truncate and rerun&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Automatic checkpointing&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Partial / sparse updates&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;COALESCE() on every column&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;IGNORE NULL UPDATES&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Selective history tracking&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Custom checksums on selected columns&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;TRACK HISTORY ON&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Lines of code&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;150+&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;~7&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;H2&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2&gt;&lt;STRONG&gt;AUTO CDC Syntax at a Glance&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;The syntax is built around a few intuitive building blocks:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG style="color: #1b3139; font-family: inherit;"&gt;`KEYS`&lt;/STRONG&gt;&lt;SPAN&gt; -- The business key(s) that uniquely identify a record&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;`SEQUENCE BY`&lt;/STRONG&gt;&lt;SPAN&gt; -- The column that determines record order (timestamp, sequence number, etc.)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG style="color: #1b3139; font-family: inherit;"&gt;`STORED AS SCD TYPE 1|2` &lt;/STRONG&gt;-- Whether to keep only the latest version or full history (default: SCD Type 1)&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG style="color: #1b3139; font-family: inherit;"&gt;`APPLY AS DELETE WHEN`&lt;/STRONG&gt;&lt;SPAN&gt; -- A condition that marks records as deletes&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;SCD Type 1 is the default, and that is a deliberate design choice. The most common CDC problem is deceptively simple: keep a "golden record" table that always reflects the latest state of each entity. Think of a customer dimension that powers your dashboards and reports -- you do not need every historical version, you just need the current truth. But as we saw earlier, even this "simple" case gets complicated fast with MERGE INTO when you factor in duplicate keys, out-of-order arrivals, and delete handling. AUTO CDC makes the common case effortless.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Here is what SCD Type 1 looks like -- keep only the latest version of each booking:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REFRESH STREAMING TABLE bookings_current
  FLOW AUTO CDC
  FROM STREAM samples.wanderbricks.booking_updates
  KEYS (booking_id)
  SEQUENCE BY updated_at
  STORED AS SCD TYPE 1;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Seven lines. No dedup logic. No out-of-order handling. No error recovery code. It just works.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;But sometimes the current state is not enough. Regulatory audits need to know what a customer's address was on a specific date. Finance teams need to reconstruct metrics as they existed at quarter-close. Fraud investigations need to trace every change to an account. That is when you upgrade from the default to SCD Type 2 -- and the only change is one line:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REFRESH STREAMING TABLE bookings_history
  FLOW AUTO CDC
  FROM STREAM samples.wanderbricks.booking_updates
  KEYS (booking_id)
  SEQUENCE BY updated_at
  STORED AS SCD TYPE 2;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;The only difference from the default SCD Type 1 is the last line. AUTO CDC automatically adds __START_AT and __END_AT columns to track when each version of a record was active. The current version has __END_AT = NULL. You start with Type 1 as the default, and when the business need arises, upgrading to full history is a one-line change -- not a rewrite.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Want to avoid version bloat? Use TRACK HISTORY ON to control which columns trigger new history rows. For example, if changes to city should not create a new version:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REFRESH STREAMING TABLE bookings_history_selective
  FLOW AUTO CDC
  FROM STREAM samples.wanderbricks.booking_updates
  KEYS (booking_id)
  SEQUENCE BY updated_at
  STORED AS SCD TYPE 2
  TRACK HISTORY ON * EXCEPT (city);&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;With this configuration, changes to city update the current row in place, while changes to any other column create a new historical version. This keeps your history table focused on the changes that actually matter to your business.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;Building an SCD Type 2 Model in your Data Warehouse&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Let's build a complete example from scratch using the Databricks SQL Query Editor connected to a SQL warehouse. Every step below runs directly in the Query Editor -- no notebooks, no pipeline UI, no Python. We will create a customer table with Change Data Feed (CDF) enabled, simulate real-world changes -- inserts, updates, and deletes -- and watch AUTO CDC handle all of it declaratively.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Step 1: Create a source table with Change Data Feed enabled&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Change Data Feed captures row-level changes on a Delta table. This is our CDC source.&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REPLACE TABLE mycatalog.default.customers (
  customer_id INT,
  first_name STRING,
  last_name STRING,
  email STRING,
  city STRING
)
TBLPROPERTIES (delta.enableChangeDataFeed = true);&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;STRONG&gt;Step 2: Insert initial records&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Load 10 customers as our starting dataset:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;INSERT INTO mycatalog.default.customers VALUES
  (1,  'Alice',  'Johnson',  'alice.johnson@example.com',  'Seattle'),
  (2,  'Bob',    'Smith',    'bob.smith@example.com',      'Portland'),
  (3,  'Carol',  'Williams', 'carol.williams@example.com', 'Denver'),
  (4,  'David',  'Brown',    'david.brown@example.com',    'Austin'),
  (5,  'Eve',    'Davis',    'eve.davis@example.com',      'Chicago'),
  (6,  'Frank',  'Miller',   'frank.miller@example.com',   'Boston'),
  (7,  'Grace',  'Wilson',   'grace.wilson@example.com',   'Miami'),
  (8,  'Hank',   'Moore',    'hank.moore@example.com',     'Atlanta'),
  (9,  'Ivy',    'Taylor',   'ivy.taylor@example.com',     'Dallas'),
  (10, 'Jack',   'Anderson', 'jack.anderson@example.com',  'Phoenix');&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Step 3: Create the SCD Type 2 streaming table with AUTO CDC&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is the core of the entire pipeline -- and it is just one statement:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;CREATE OR REFRESH STREAMING TABLE mycatalog.default.customers_scd2
  SCHEDULE REFRESH EVERY 1 DAY
  FLOW AUTO CDC
  FROM STREAM mycatalog.default.customers
    WITH (readChangeFeed = true)
  KEYS (customer_id)
  APPLY AS DELETE WHEN _change_type = 'delete'
  SEQUENCE BY _commit_timestamp
  COLUMNS * EXCEPT (_change_type, _commit_version, _commit_timestamp)
  STORED AS SCD TYPE 2;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Let's break down what each line does:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`FROM STREAM ... WITH (readChangeFeed = true)`&lt;/STRONG&gt;&lt;SPAN&gt; -- Reads the Change Data Feed from the source table, which provides row-level insert, update, and delete events.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`KEYS (customer_id)`&lt;/STRONG&gt;&lt;SPAN&gt; -- Identifies each customer uniquely.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`APPLY AS DELETE WHEN _change_type = 'delete'`&lt;/STRONG&gt;&lt;SPAN&gt; -- When CDF reports a delete, AUTO CDC closes the record's history (sets &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT&lt;/SPAN&gt;&lt;SPAN&gt;) rather than leaving a ghost row.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`SEQUENCE BY _commit_timestamp`&lt;/STRONG&gt;&lt;SPAN&gt; -- Uses the CDF commit timestamp to order changes correctly -- even if they arrive out of sequence.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`COLUMNS * EXCEPT (...)`&lt;/STRONG&gt;&lt;SPAN&gt; -- Strips out the CDF metadata columns so the target table contains only business data.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;`STORED AS SCD TYPE 2`&lt;/STRONG&gt;&lt;SPAN&gt; -- Maintains full history. Every change creates a new version with &lt;/SPAN&gt;&lt;SPAN&gt;__START_AT&lt;/SPAN&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT&lt;/SPAN&gt;&lt;SPAN&gt; tracking. If you want to only maintain the current version, simply swap this out for `STORED AS SCD TYPE 1.`&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;Step 4: Simulate real-world changes&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now let's simulate what happens in a production system -- new customers sign up, existing customers update their information, and some customers are removed.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Insert 2 new customers:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;INSERT INTO mycatalog.default.customers VALUES
  (11, 'Karen', 'Thomas',  'karen.thomas@example.com',  'San Francisco'),
  (12, 'Leo',   'Jackson', 'leo.jackson@example.com',   'New York');&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Update 2 existing customers -- Alice moved to Los Angeles, Bob changed his email:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;UPDATE mycatalog.default.customers SET city = 'Los Angeles' WHERE customer_id = 1;
UPDATE mycatalog.default.customers SET email = 'bob.updated@example.com' WHERE customer_id = 2;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Delete 2 customers:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;DELETE FROM mycatalog.default.customers WHERE customer_id = 9;
DELETE FROM mycatalog.default.customers WHERE customer_id = 10;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;STRONG&gt;Step 5: Refresh and query the results&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Refresh the streaming table to process all the changes:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;REFRESH STREAMING TABLE mycatalog.default.customers_scd2;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Now query the SCD Type 2 table:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE border="1" width="100%"&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="100%"&gt;&lt;LI-CODE lang="markup"&gt;SELECT * FROM mycatalog.default.customers_scd2;&lt;/LI-CODE&gt;&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CarlosR_1-1776958918597.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/26361iB6449AFF40FCC2A9/image-size/large?v=v2&amp;amp;px=999" role="button" title="CarlosR_1-1776958918597.png" alt="CarlosR_1-1776958918597.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Here is what you will see in the results:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Customers 3-8&lt;/STRONG&gt;&lt;SPAN&gt; -- Unchanged. One row each with &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT = NULL&lt;/SPAN&gt;&lt;SPAN&gt; (current and active).&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Customer 1 (Alice)&lt;/STRONG&gt;&lt;SPAN&gt; -- Two rows. The original Seattle version has a &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT&lt;/SPAN&gt;&lt;SPAN&gt; timestamp marking when it was superseded. The new Los Angeles version has &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT = NULL&lt;/SPAN&gt;&lt;SPAN&gt; -- she is current.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Customer 2 (Bob)&lt;/STRONG&gt;&lt;SPAN&gt; -- Two rows. Same pattern -- the old email version is closed, the new email version is current.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Customers 9 and 10 (Ivy and Jack)&lt;/STRONG&gt;&lt;SPAN&gt; -- Their records have &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT&lt;/SPAN&gt;&lt;SPAN&gt; set to the deletion timestamp. They are closed -- not deleted from the table, but historically tracked as removed.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Customers 11 and 12 (Karen and Leo)&lt;/STRONG&gt;&lt;SPAN&gt; -- One row each with &lt;/SPAN&gt;&lt;SPAN&gt;__END_AT = NULL&lt;/SPAN&gt;&lt;SPAN&gt;. New additions, fully tracked from their first appearance.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;No dedup logic. No checksums. No window functions. No multi-arm MERGE. AUTO CDC handled all of it -- inserts, updates, deletes, and history tracking -- from a single declarative statement.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;When to Use AUTO CDC&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;AUTO CDC is a natural fit any time you are building dimensional tables from change data:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;CDC feeds from operational databases&lt;/STRONG&gt;&lt;SPAN&gt; -- Whether you are consuming Change Data Feed from Delta tables, Debezium streams, or any source that provides row-level changes, AUTO CDC handles the complexity of turning those changes into clean dimensions.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Replacing existing MERGE-based SCD pipelines&lt;/STRONG&gt;&lt;SPAN&gt; -- If you have existing pipelines with the 150-line MERGE pattern, AUTO CDC is a straightforward modernization path. Same result, a fraction of the code, and better error recovery.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Out-of-order or late-arriving data&lt;/STRONG&gt;&lt;SPAN&gt; -- If your source data does not arrive in perfect chronological order -- and it rarely does in production -- AUTO CDC's &lt;/SPAN&gt;&lt;SPAN&gt;SEQUENCE BY&lt;/SPAN&gt;&lt;SPAN&gt; handles this automatically instead of silently corrupting your dimensions.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Note: What about sources without a change feed? Many legacy databases and JDBC sources only provide periodic snapshots, not row-level change events. AUTO CDC FROM SNAPSHOT -- which automatically infers inserts, updates, and deletes by comparing successive snapshots -- is available today in Spark Declarative Pipelines via Python. SQL support is not yet available but is planned for a future release.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;Requirements&lt;/STRONG&gt;&lt;/H3&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Compute: &lt;/STRONG&gt;Serverless SQL Warehouse, or SDP Pro / Advanced edition&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Runtime&lt;/STRONG&gt;&lt;SPAN&gt;: Databricks Runtime 17.3 or later&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;STRONG&gt;Get Started&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;AUTO CDC is available today in Beta. Here is how to get started:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/sql/get-started/sql-etl-tutorial" target="_self"&gt;Incremental ETL tutorial&lt;/A&gt;&amp;nbsp;-- Tutorial on how to run ETL inside Databricks SQL&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://docs.databricks.com/aws/en/ldp/dbsql/streaming#apply-change-data-capture-cdc-with-auto-cdc-flows" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;AUTO CDC in Databricks SQL Documentation&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; -- Full syntax reference, parameters, and examples&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;A href="https://docs.databricks.com/aws/en/ldp/dbsql/streaming" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Streaming Tables in Databricks SQL&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; -- Background on streaming tables and flows in DBSQL&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Try it with your own data -- swap out the mycatalog.default.customers table for one of your CDC sources, and see how much procedural code you can retire. Take an existing MERGE-based SCD pipeline, rewrite it with AUTO CDC, and compare the results. We think you will be surprised how much complexity disappears.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;If you have feedback or questions, drop them in the comments below.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Fri, 24 Apr 2026 14:14:54 GMT</pubDate>
    <dc:creator>CarlosR</dc:creator>
    <dc:date>2026-04-24T14:14:54Z</dc:date>
    <item>
      <title>From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/ba-p/155355</link>
      <description>&lt;P&gt;&lt;A href="https://docs.databricks.com/aws/en/ldp/what-is-change-data-capture" target="_self"&gt;https://docs.databricks.com/aws/en/ldp/what-is-change-data-capture&lt;/A&gt;&amp;nbsp;&lt;A href="https://docs.databricks.com/aws/en/ldp/what-is-change-data-capture" target="_self"&gt;https://docs.databricks.com/aws/en/ldp/what-is-change-data-capture&lt;/A&gt;&amp;nbsp;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2026-04-23 at 16.13.06.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/26363iE2DCA61E7E8E1DCA/image-size/large?v=v2&amp;amp;px=999" role="button" title="Screenshot 2026-04-23 at 16.13.06.png" alt="Screenshot 2026-04-23 at 16.13.06.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN class="appsElementsGenerativeaiAstAnimated" data-ast-node-id="6"&gt;Time to stop writing 150+ lines of fragile&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;CODE class="appsElementsGenerativeaiAstInlineCode"&gt;MERGE INTO&lt;/CODE&gt;&lt;SPAN class="appsElementsGenerativeaiAstAnimated" data-ast-node-id="8"&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;SQL for Change Data Capture (CDC) workloads that don't handle out-of-order records or deletes reliably. Introducing AUTO CDC in Databricks SQL, the new declarative API that simplifies complex SCD Type 1 and Type 2 logic into just 7 lines of code. Read how you can eliminate technical debt, automatically manage data history, ordering, and error recovery, and modernize your data warehouse ETL today&lt;/SPAN&gt;&lt;SPAN class="appsElementsGenerativeaiAstAnimated" data-ast-node-id="9"&gt;.&lt;/SPAN&gt;&lt;/P&gt;</description>
      <pubDate>Fri, 24 Apr 2026 14:14:54 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/ba-p/155355</guid>
      <dc:creator>CarlosR</dc:creator>
      <dc:date>2026-04-24T14:14:54Z</dc:date>
    </item>
    <item>
      <title>Re: From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155469#M1024</link>
      <description>&lt;P&gt;With standard tables we have the possibility to use columns with identity (sequence like) value. From the modeling perspective surrogate keys are considered as a good practice. How autocdc handles surrogate keys? You probably don't want your BI tool of choice join the facts and dimension tables on business keys as especially in SCD2 they are not unique.&lt;/P&gt;</description>
      <pubDate>Fri, 24 Apr 2026 21:11:03 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155469#M1024</guid>
      <dc:creator>pepco-personal</dc:creator>
      <dc:date>2026-04-24T21:11:03Z</dc:date>
    </item>
    <item>
      <title>Re: From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155474#M1025</link>
      <description>&lt;P&gt;Thank you for such an informative post on one of my favourite topics—&lt;STRONG&gt;MERGE INTO&lt;/STRONG&gt;.&lt;/P&gt;&lt;P&gt;I completely agree that &lt;STRONG&gt;AUTO CDC&lt;/STRONG&gt; significantly simplifies handling both SCD Type 1 and Type 2 patterns. The side-by-side comparison between a manual MERGE INTO implementation and AUTO CDC makes the difference very clear. From a developer’s perspective, AUTO CDC not only expedites the build phase but also makes long-term maintenance much easier.&lt;/P&gt;&lt;P&gt;That said, while I agree with most of the advantages highlighted, I’m not entirely convinced that &lt;EM&gt;“Error recovery”&lt;/EM&gt; is a distinct benefit of AUTO CDC over a manual MERGE INTO approach. My understanding is that MERGE INTO operations are atomic—either the entire transaction succeeds or it fails without partial updates. Based on that, I would expect error recovery characteristics to be similar.&lt;/P&gt;&lt;P&gt;I’d be interested to hear your thoughts on this in case I’m overlooking something.&lt;/P&gt;&lt;P&gt;Thanks again for sharing such valuable insights.&lt;/P&gt;</description>
      <pubDate>Sat, 25 Apr 2026 01:43:42 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155474#M1025</guid>
      <dc:creator>Surya2</dc:creator>
      <dc:date>2026-04-25T01:43:42Z</dc:date>
    </item>
    <item>
      <title>Re: From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155578#M1027</link>
      <description>&lt;P&gt;Thanks for commenting both!&lt;/P&gt;
&lt;P&gt;We're working on adding identity column support for AUTO CDC! As of right now, we recommend adding the surrogate key in the source table before AUTO CDC.&amp;nbsp;&lt;BR /&gt;&lt;BR /&gt;You're correct that MERGE INTO itself is atomic, but if a batch is incorrect but still successfully processed (for example, duplicates, out-of-order updates, or partial data), a manual MERGE can leave the table in a wrong state—and recovery often means truncating and rerunning. AUTO CDC reduces that risk by handling ordering, deduplication, and idempotency upfront, and then uses checkpointing to safely resume if something does fail.&lt;/P&gt;</description>
      <pubDate>Mon, 27 Apr 2026 15:32:39 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155578#M1027</guid>
      <dc:creator>shanelleroman97</dc:creator>
      <dc:date>2026-04-27T15:32:39Z</dc:date>
    </item>
    <item>
      <title>Re: From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155629#M1029</link>
      <description>&lt;P&gt;Thank you&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/226548"&gt;@shanelleroman97&lt;/a&gt;&amp;nbsp;for the clarifications.&lt;/P&gt;</description>
      <pubDate>Tue, 28 Apr 2026 00:09:55 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155629#M1029</guid>
      <dc:creator>Surya2</dc:creator>
      <dc:date>2026-04-28T00:09:55Z</dc:date>
    </item>
    <item>
      <title>Re: From 150 Lines of MERGE INTO to 7 Lines of SQL: AUTO CDC Comes to Databricks SQL</title>
      <link>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155957#M1035</link>
      <description>&lt;BLOCKQUOTE&gt;&lt;HR /&gt;&lt;SPAN&gt;We're working on adding identity column support for AUTO CDC! As of right now, we recommend adding the surrogate key in the source table before AUTO CDC.&amp;nbsp;&lt;/SPAN&gt;&lt;/BLOCKQUOTE&gt;&lt;P&gt;With regards to this, it might not be possible or even desired to add this complexity to the source table. uuid might be an alternative but the nice thing about identity is that you don't need to think about them too much + they are integers so by default have smaller footprint.&lt;/P&gt;&lt;P&gt;Also when we are talking about table, are there some more complex examples or some migration from complex batch processing? It's all nice to see how this works with simple tables, but often you need to join, transform, clean, aggregate data etc. before you actually write to the target table. I probably lack imagination, but then again I consider myself still the beginner.&lt;/P&gt;</description>
      <pubDate>Fri, 01 May 2026 17:30:48 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/from-150-lines-of-merge-into-to-7-lines-of-sql-auto-cdc-comes-to/bc-p/155957#M1035</guid>
      <dc:creator>pepco-personal</dc:creator>
      <dc:date>2026-05-01T17:30:48Z</dc:date>
    </item>
  </channel>
</rss>

