Note
ამ გვერდზე წვდომა ავტორიზაციას მოითხოვს. შეგიძლიათ სცადოთ შესვლა ან დირექტორიების შეცვლა.
ამ გვერდზე წვდომა ავტორიზაციას მოითხოვს. შეგიძლიათ სცადოთ დირექტორიების შეცვლა.
Beyond the basic AUTO CDC and AUTO CDC FROM SNAPSHOT APIs, you can run DML on target tables, read change data feeds from CDC targets, monitor processing metrics, apply partial updates, and track changes with bitemporal storage. For an introduction to the AUTO CDC APIs, see The AUTO CDC APIs: Simplify change data capture with pipelines.
Add, change, or delete data in a target streaming table
If your pipeline publishes tables to Unity Catalog, you can use data manipulation language (DML) statements, including insert, update, delete, and merge statements, to modify the target streaming tables created by AUTO CDC ... INTO statements.
Note
- DML statements that modify the table schema of a streaming table are not supported. Ensure that your DML statements do not attempt to evolve the table schema.
- DML statements that update a streaming table can be run only in a shared Unity Catalog cluster or a SQL warehouse using Databricks Runtime 13.3 LTS and above.
- Because streaming requires append-only data sources, if your processing requires streaming from a source streaming table with changes (for example, by DML statements), set the skipChangeCommits flag when reading the source streaming table. When
skipChangeCommitsis set, transactions that delete or modify records on the source table are ignored. If your processing does not require a streaming table, you can use a materialized view (which does not have the append-only restriction) as the target table.
Because the pipeline uses a specified SEQUENCE BY column and propagates appropriate sequencing values to the __START_AT and __END_AT columns of the target table (for SCD Type 2), you must ensure that DML statements use valid values for these columns to maintain the proper ordering of records. See How AUTO CDC works.
For more information about using DML statements with streaming tables, see Add, change, or delete data in a streaming table.
The following example inserts an active record with a start sequence of 5:
INSERT INTO my_streaming_table (id, name, __START_AT, __END_AT) VALUES (123, 'John Doe', 5, NULL);
Tip
If you need to rename the __START_AT and __END_AT columns in your SCD Type 2 target table (for example, to match downstream schema requirements), create a view over the target table:
CREATE VIEW my_employees_view AS
SELECT
*,
__START_AT AS valid_from,
__END_AT AS valid_to
FROM my_scd2_target_table;
Read a change data feed from an AUTO CDC target table
In Databricks Runtime 15.2 and above, you can read a change data feed from a streaming table that is the target of AUTO CDC or AUTO CDC FROM SNAPSHOT queries in the same way that you read a change data feed from other Delta tables. The following are required to read the change data feed from a target streaming table:
- The target streaming table must be published to Unity Catalog. See Use Unity Catalog with pipelines.
- To read the change data feed from the target streaming table, you must use Databricks Runtime 15.2 or above. To read the change data feed in a different pipeline, the pipeline must be configured to use Databricks Runtime 15.2 or above.
You read the change data feed from a target streaming table that was created in a Lakeflow pipeline the same way as reading a change data feed from other Delta tables. To learn more about using the Delta change data feed functionality, including examples in Python and SQL, see Use change data feed on Azure Databricks.
Note
The change data feed record includes metadata identifying the type of change event. When a record is updated in a table, the metadata for the associated change records typically includes _change_type values set to update_preimage and update_postimage events.
However, the _change_type values are different if updates are made to the target streaming table that include changing primary key values. When changes include updates to primary keys, the _change_type metadata fields are set to insert and delete events. Changes to primary keys can occur when manual updates are made to one of the key fields with an UPDATE or MERGE statement or, for SCD type 2 tables, when the __start_at field changes to reflect an earlier start sequence value.
The AUTO CDC query determines the primary key values, which differ for SCD type 1 and SCD type 2 processing:
| SCD Type | Primary key |
|---|---|
| SCD type 1, and the pipelines Python interface | The primary key is the value of the keys parameter in the create_auto_cdc_flow() function. For the SQL interface the primary key is the columns defined by the KEYS clause in the AUTO CDC ... INTO statement. |
| SCD type 2 | The primary key is the keys parameter or KEYS clause plus the return value from the coalesce(__START_AT, __END_AT) operation, where __START_AT and __END_AT are the corresponding columns from the target streaming table. This uses __START_AT when available, and __END_AT when __START_AT is null (for example, the initial record). |
Get data about records processed by a CDC query in pipelines
Note
The following metrics are captured only by AUTO CDC queries and not by AUTO CDC FROM SNAPSHOT queries.
The following metrics are captured by AUTO CDC queries:
num_upserted_rows: The number of output rows upserted into the dataset during an update.num_deleted_rows: The number of existing output rows deleted from the dataset during an update.
The num_output_rows metric, output for non-CDC flows, is not captured for AUTO CDC queries.
Apply partial updates
When a source sends only the columns that changed, AUTO CDC must distinguish between a column that is absent from a change record, which should leave the target value unchanged, and a column that is explicitly set to null, which should overwrite the target value with null. By default, IGNORE NULL UPDATES treats every null as a "do not update" marker, so it cannot apply an explicit null. To resolve this ambiguity, choose one of the following three methods:
| Method | When to use | Behavior |
|---|---|---|
IGNORE NULL UPDATES ON columnList |
A small, fixed set of columns should ignore null values, while all other columns apply explicit null values. |
The listed columns keep their existing target value when the incoming value is null. All other columns apply explicit null values. |
IGNORE NULL UPDATES ON * EXCEPT (exceptColumnList) |
Most columns should ignore null values, and only a few should apply explicit null values. |
The listed columns apply explicit null values. All other columns keep their existing target value when the incoming value is null. |
COLUMNS TO UPDATE |
Each change record updates a different set of columns, or the set of updatable columns changes over time. | A source column names the columns to update for each change record. The listed columns are written from the source, including explicit null values. Columns that are not listed keep their existing target value. |
COLUMNS TO UPDATE cannot be combined with IGNORE NULL UPDATES, and is not supported for bitemporal tables.
As a rule of thumb, choose COLUMNS TO UPDATE when the producer knows which columns changed in each record and can carry that information in a source column, such as when multiple producers write to the same source or the set of updatable columns grows over time. Choose IGNORE NULL UPDATES ON when the pipeline owner knows the fixed set of updatable columns in advance and prefers to control them in pipeline code.
The following example uses a source column named columnsToUpdate to control which columns each change record updates, including columns set explicitly to null:
Python
from pyspark import pipelines as dp
dp.create_streaming_table("target")
dp.create_auto_cdc_flow(
target = "target",
source = "cdc_source",
keys = ["id"],
sequence_by = "sequenceNum",
stored_as_scd_type = 1,
columns_to_update = "columnsToUpdate"
)
SQL
CREATE OR REFRESH STREAMING TABLE target;
CREATE FLOW apply_cdc AS AUTO CDC INTO
target
FROM
stream(cdc_source)
KEYS
(id)
SEQUENCE BY
sequenceNum
STORED AS
SCD TYPE 1
COLUMNS TO UPDATE
columnsToUpdate;
For the full parameter reference, see AUTO CDC INTO (pipelines) and create_auto_cdc_flow.
Bitemporal AUTO CDC
Important
Bitemporal AUTO CDC is in Beta.
SCD Type 1 and Type 2 are unitemporal: they track changes across a single time dimension. Bitemporal extends SCD Type 2 history to track changes across two time dimensions and distinguish between two perspectives:
- Business time: when the event actually happened.
- System time: when the system recorded or ingested the event.
Like SCD Type 2, bitemporal preserves a full history of records. It adds a second timeline so you can reconstruct both what the data showed and what the system believed at any point in the past.
For example, a hedge fund ingests stock data from a source system. Acme Corp's stock price changes on January 1, but the fund does not ingest that update until January 5. Bitemporal AUTO CDC lets the fund answer two distinct questions: what Acme Corp's actual stock price was on January 1 (business time), and what price the system believed when the fund made trading decisions on January 3 (system time). The ability to distinguish between these timelines is useful for auditing, regulatory reporting, and financial decision-making.
To enable bitemporal processing, set STORED AS BITEMPORAL (SQL) or stored_as_scd_type="bitemporal" (Python), use SEQUENCE BY for the business time column, and use SYSTEM SEQUENCE BY for the system time column. The target table adds __SYSTEM_START_AT and __SYSTEM_END_AT columns alongside the SCD Type 2 __START_AT and __END_AT columns. For syntax details, see AUTO CDC INTO (pipelines) or create_auto_cdc_flow.
Bitemporal AUTO CDC examples
The following example creates a bitemporal target table from a small set of synthetic CDC events. The bt column has the business time and the st column has the system time.
Python
from pyspark import pipelines as dp
# Source: synthetic CDC events
dp.create_streaming_table(name="cdc_source")
@dp.append_flow(target="cdc_source", once=True)
def load_cdc_source():
return spark.createDataFrame(
[
(1, "x10", "y10", 10, 100),
(1, "x20", "y20", 20, 200)
],
schema="id INT, x STRING, y STRING, bt INT, st INT",
)
# Target: bitemporal table
dp.create_streaming_table(name="target_bitemporal")
dp.create_auto_cdc_flow(
target = "target_bitemporal",
source = "cdc_source",
keys = ["id"],
sequence_by = "bt",
system_sequence_by = "st",
stored_as_scd_type = "bitemporal"
)
SQL
-- Source: synthetic CDC events
CREATE OR REFRESH STREAMING TABLE cdc_source_sql;
CREATE FLOW cdc_source_sql AS INSERT INTO ONCE
cdc_source_sql BY NAME
SELECT * FROM VALUES
(1, 'x10', 'y10', 10, 100),
(1, 'x20', 'y20', 20, 200)
AS t(id, x, y, bt, st);
-- Target: bitemporal table
CREATE OR REFRESH STREAMING TABLE target_bitemporal_sql;
CREATE FLOW target_bitemporal_sql AS AUTO CDC INTO
target_bitemporal_sql
FROM
stream(cdc_source_sql)
KEYS
(id)
SEQUENCE BY
bt
SYSTEM SEQUENCE BY
st
STORED AS
BITEMPORAL;
The following sequence of changes shows how a bitemporal table records an insert, an update, an out-of-order update, and a delete for a single company. The sequencing column generates the __START_AT and __END_AT (business time) columns, and the system sequencing column generates the __SYSTEM_START_AT and __SYSTEM_END_AT (system time) columns:
| Column | Description |
|---|---|
__START_AT |
The business time at which this row became valid. |
__END_AT |
The business time at which this row's validity ends. null if valid indefinitely. |
__SYSTEM_START_AT |
The system time at which this row's data and business time interval are known to be true. |
__SYSTEM_END_AT |
The system time at which this row's data and business time interval are known to be invalidated. null if known to be true indefinitely. |
The system handles events that arrive in any order across both timelines. When an event arrives with an earlier business time or system time than events already processed, the system corrects the affected history rather than appending only to the end.
Change 1: Insert
Company A is added at 7/18/2025 10:01:00 (business time) but is not ingested until 10:05:00 (system time).
Input:
| CompanyId | Data Point | Sequencing | System Sequencing | Operation |
|---|---|---|---|---|
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 10:05:00 | INSERT |
Output:
| CompanyId | Data Point | __START_AT | __END_AT | __SYSTEM_START_AT | __SYSTEM_END_AT |
|---|---|---|---|---|---|
| A | XFv1 | 7/18/2025 10:01:00 | NULL | 7/18/2025 10:05:00 | NULL |
XFv1 is valid starting at 10:01:00 with no known end. The system learned of this fact at system time 10:05:00, with no known end.
Change 2: Update
Company A is updated at 7/18/2025 12:15:43 (business time), and the system consumes the event at 12:20:00 (system time). The system preserves both what it believed before the update was known and the corrected business history after the update is ingested.
Input:
| CompanyId | Data Point | Sequencing | System Sequencing | Operation |
|---|---|---|---|---|
| A | XFv2 | 7/18/2025 12:15:43 | 7/18/2025 12:20:00 | UPDATE |
Output:
| CompanyId | Data Point | __START_AT | __END_AT | __SYSTEM_START_AT | __SYSTEM_END_AT |
|---|---|---|---|---|---|
| A | XFv1 | 7/18/2025 10:01:00 | NULL | 7/18/2025 10:05:00 | 7/18/2025 12:20:00 |
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 12:15:43 | 7/18/2025 12:20:00 | NULL |
| A | XFv2 | 7/18/2025 12:15:43 | NULL | 7/18/2025 12:20:00 | NULL |
XFv1 was believed valid from 10:01:00 with no known end, and the system held that belief from 10:05:00 until 12:20:00. XFv1 is now known to be valid only until 12:15:43, a corrected history effective from system time 12:20:00 with no known end. XFv2 is valid starting at 12:15:43 with no known end, and was learned at system time 12:20:00.
Change 3: Out-of-order update
An out-of-order update arrives indicating that Company A was actually updated at 7/18/2025 12:05:00 (business time), but it is not ingested until 12:25:00 (system time). When an update arrives later in system time but with a preceding business time, the system corrects the historical business time and preserves both what it believed before the out-of-order update and the corrected history.
Input:
| CompanyId | Data Point | Sequencing | System Sequencing | Operation |
|---|---|---|---|---|
| A | XFv3 | 7/18/2025 12:05:00 | 7/18/2025 12:25:00 | UPDATE |
Output:
| CompanyId | Data Point | __START_AT | __END_AT | __SYSTEM_START_AT | __SYSTEM_END_AT |
|---|---|---|---|---|---|
| A | XFv1 | 7/18/2025 10:01:00 | NULL | 7/18/2025 10:05:00 | 7/18/2025 12:20:00 |
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 12:15:43 | 7/18/2025 12:20:00 | 7/18/2025 12:25:00 |
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 12:05:00 | 7/18/2025 12:25:00 | NULL |
| A | XFv3 | 7/18/2025 12:05:00 | 7/18/2025 12:15:43 | 7/18/2025 12:25:00 | NULL |
| A | XFv2 | 7/18/2025 12:15:43 | NULL | 7/18/2025 12:20:00 | NULL |
XFv1 was believed valid from 10:01:00 to 12:15:43, and that belief is now valid in system time up to 12:25:00. The new update corrects XFv1's business validity to end at 12:05:00, a corrected history effective from system time 12:25:00. XFv3 is now known to be valid from 12:05:00 until 12:15:43, a belief valid in system time from 12:25:00 with no known end.
Change 4: Delete
Company A is deleted at 7/18/2025 12:30:00, and the system consumes the event at 12:30:00. Because a delete operation represents the end of the entity's business existence, the system creates no replacement row. XFv2 appears in two rows, preserving a complete audit trail of both when the company ceased to exist and when the system learned of the deletion.
Input:
| CompanyId | Data Point | Sequencing | System Sequencing | Operation |
|---|---|---|---|---|
| A | XFv2 | 7/18/2025 12:30:00 | 7/18/2025 12:30:00 | DELETE |
Output:
| CompanyId | Data Point | __START_AT | __END_AT | __SYSTEM_START_AT | __SYSTEM_END_AT |
|---|---|---|---|---|---|
| A | XFv1 | 7/18/2025 10:01:00 | NULL | 7/18/2025 10:05:00 | 7/18/2025 12:20:00 |
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 12:15:43 | 7/18/2025 12:20:00 | 7/18/2025 12:25:00 |
| A | XFv1 | 7/18/2025 10:01:00 | 7/18/2025 12:05:00 | 7/18/2025 12:25:00 | NULL |
| A | XFv3 | 7/18/2025 12:05:00 | 7/18/2025 12:15:43 | 7/18/2025 12:25:00 | NULL |
| A | XFv2 | 7/18/2025 12:15:43 | NULL | 7/18/2025 12:20:00 | 7/18/2025 12:30:00 |
| A | XFv2 | 7/18/2025 12:15:43 | 7/18/2025 12:30:00 | 7/18/2025 12:30:00 | NULL |
XFv2 was valid from 12:15:43 with no known end, and the system held that belief from 12:20:00 to 12:30:00. After the delete is ingested, XFv2 is known to be valid only until 12:30:00, a corrected history effective from system time 12:30:00.
What data objects are used for CDC processing in a pipeline?
When you declare the target table in the Hive metastore, two data structures are created:
- A view using the name assigned to the target table.
- An internal backing table used by the pipeline to manage CDC processing. This table is named by prepending
__apply_changes_storage_to the target table name.
For example, if you declare a target table named dp_cdc_target, you see a view named dp_cdc_target and a table named __apply_changes_storage_dp_cdc_target in the metastore. Query the view to access the processed data. Do not modify the backing table directly.
Note
These data structures apply only to AUTO CDC processing, not AUTO CDC FROM SNAPSHOT processing. They also apply only to Hive metastore, not Unity Catalog.