CertSafari
    Databricks Certified Data Engineer Professional· Lessons

    Domain 1 · Lesson 7/46

    SCD Type 2 History with stored_as_scd_type in AUTO CDC

    Implement CDC pipelines using AUTO CDC APIs (formerly APPLY CHANGES) in Lakeflow Declarative Pipelines, including SCD Type 1 and Type 2 via stored_as_scd_type.

    10 min read
    1.92% of exam
    4 sources
    Published 2 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Configure an AUTO CDC flow to keep SCD Type 2 history in Python and SQL
    • Read __START_AT and __END_AT to tell current rows from historical versions, including after deletes
    • Limit history to selected columns with track_history_column_list or TRACK HISTORY ON, and recognise the operations and schema rules specific to Type 2

    1.Switching a flow to SCD Type 2

    An AUTO CDC flow writes either an in-place SCD Type 1 table or an SCD Type 2 history table, and one parameter decides which: stored_as_scd_type in Python, STORED AS in SQL. Set it to 1 for Type 1 or 2 for Type 2; the parameter accepts a string or an int. Type 2 retains a history of records. That can be on all updates, or only on updates to a set of columns you choose.

    Instead of overwriting, Type 2 adds a new row for each version of a record. Two columns record when each version was active: __START_AT and __END_AT. Their values come from your sequencing column. In the flow below, the source view, keys, sequencing and delete condition are the standard documentation example. What makes it Type 2 is stored_as_scd_type = "2" and the absence of a truncate condition.

    SCD Type 2 in Python: note stored_as_scd_type = "2" and the absence of apply_as_truncatespython
    from pyspark import pipelines as dp
    from pyspark.sql.functions import col, expr
    
    @dp.view
    def users():
      return spark.readStream.table("main.cdc_tutorial.users_cdf")
    
    dp.create_streaming_table("users_history")
    
    dp.create_auto_cdc_flow(
      target = "users_history",
      source = "users",
      keys = ["userId"],
      sequence_by = col("sequenceNum"),
      apply_as_deletes = expr("operation = 'DELETE'"),
      except_column_list = ["operation", "sequenceNum"],
      stored_as_scd_type = "2"
    )

    Checkpoint 1 of 4· Fill the gap

    Complete the SQL flow so that it keeps a full history of every user's changes.

    CREATE OR REFRESH STREAMING TABLE users_history;
    
    CREATE FLOW apply_cdc AS AUTO CDC INTO
      users_history
    FROM
      stream(main.cdc_tutorial.users_cdf)
    KEYS
      (userId)
    APPLY AS DELETE WHEN
      operation = "DELETE"
    SEQUENCE BY
      sequenceNum
    COLUMNS * EXCEPT
      (operation, sequenceNum)
    STORED AS
      SCD TYPE  ? ;

    Sources12

    2.Reading the history: __START_AT, __END_AT and deletes

    The documentation's sample change feed main.cdc_tutorial.users_cdf has four users with an operation column and a sequenceNum. Isabel (123) is inserted in Monterrey at sequence 1, updated to Chihuahua at 5, and deleted at 6. Mercedes (125) is inserted in Tijuana at 2, updated to Mexicali at 5, and updated to Guadalajara at 6. The Mexicali update arrives after the Guadalajara one. Raul (124) and Lily (126) are inserted and never change.

    users_history after the SCD Type 2 flow (from the documentation example)
    userIdcity__START_AT__END_AT
    123Monterrey15
    123Chihuahua56
    124Oaxaca1null
    125Tijuana25
    125Mexicali56
    125Guadalajara6null
    126Cancun2null

    The late Mexicali update isn't thrown away. It appears as a real version (5 to 6), placed in sequence order even though it arrived last. That is the difference from an in-place Type 1 table, which keeps only Mercedes' latest city. A row with __END_AT = null is the current version. A deleted key has no open row, only closed history. To get today's view of the dimension, filter on __END_AT IS NULL.

    The column names are fixed. If downstream consumers expect names like valid_from and valid_to, the documented approach is to create a view over the target that aliases __START_AT and __END_AT.

    Checkpoint 2 of 4· Check yourself

    In an AUTO CDC SCD Type 2 target, how do you identify the currently active version of each key?

    Checkpoint 3 of 4· Exam question

    A pipeline defines the following SQL flow to capture changes into a dimension table: ```sql CREATE OR REFRESH STREAMING TABLE dim_products; CREATE FLOW products_cdc_flow AS AUTO CDC INTO dim_products FROM STREAM(cdc_products) KEYS (product_id) APPLY AS TRUNCATE WHEN operation = "TRUNCATE" SEQUENCE BY sequence_num STORED AS SCD TYPE 2; ``` Pipeline validation rejects this flow definition. What is the cause?

    Sources23

    3.Type 2 rules: selective history, truncates and schemas

    By default every change to any output column opens a new version. That is equivalent to TRACK HISTORY ON *. When some columns change often but don't matter historically, limit which columns generate history with track_history_column_list / track_history_except_column_list in Python, or TRACK HISTORY ON in SQL. In the example below, a change to city alone does not create a history record.

    SCD Type 2 that excludes city from history trackingsql
    CREATE OR REFRESH STREAMING TABLE target;
    
    CREATE FLOW flow
    AS AUTO CDC INTO
      target
    FROM stream(cdc_data.users)
      KEYS (userId)
      APPLY AS DELETE WHEN operation = "DELETE"
      SEQUENCE BY sequenceNum
      COLUMNS * EXCEPT (operation, sequenceNum)
      STORED AS SCD TYPE 2
      TRACK HISTORY ON * EXCEPT (city);
    How the stored_as_scd_type choice changes what is allowed and what you get
    AspectSCD Type 1SCD Type 2
    Settingstored_as_scd_type = 1 / STORED AS SCD TYPE 1 (default)stored_as_scd_type = "2" / STORED AS SCD TYPE 2
    HistoryNot retained; values overwrittenNew row per version
    Extra columnsNone__START_AT and __END_AT
    apply_as_truncates / APPLY AS TRUNCATE WHENSupportedNot supported
    TRACK HISTORY ONNot applicableLimits which columns open a new version

    Two rules from the reference are easy to miss.

    - No truncates in Type 2. apply_as_truncates / APPLY AS TRUNCATE WHEN work only with Type 1. That's why the Type 2 example leaves the truncate condition out. - Explicit schemas must include the history columns. You can optionally declare the target table's schema yourself. If you do for a Type 2 target, it must include __START_AT and __END_AT with the same data type as the sequence_by field. So if you sequence by a timestamp, those columns are timestamps too.

    There is a third value for stored_as_scd_type: "bitemporal" (STORED AS BITEMPORAL), which is in Beta. It extends Type 2 with a second timeline for system time. It needs system_sequence_by / SYSTEM SEQUENCE BY and adds __SYSTEM_START_AT and __SYSTEM_END_AT columns. The exam objective is about Types 1 and 2; just recognise this value so it doesn't catch you out.

    Checkpoint 4 of 4· Check yourself

    A source system occasionally emits a TRUNCATE event. The team wants a full history of customer changes and also wants those TRUNCATE events to clear the table. What happens if they configure both on one AUTO CDC flow?

    Sources41

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.A DELETE in an SCD Type 2 flow removes all of the key's rows from the target.Why is that wrong?

      The delete closes the active version by setting its __END_AT. Earlier versions stay, and the key simply has no row with __END_AT null.

      Covered in Reading the history: __START_AT, __END_AT and deletes

    2. 2.APPLY AS TRUNCATE WHEN can be added to any AUTO CDC flow, including SCD Type 2.Why is that wrong?

      Truncate handling is supported only for SCD Type 1; SCD Type 2 does not support it.

      Covered in Type 2 rules: selective history, truncates and schemas

    3. 3.When you declare an explicit schema for an SCD Type 2 target, you list only your business columns and the pipeline adds __START_AT and __END_AT.Why is that wrong?

      An explicit Type 2 schema must include __START_AT and __END_AT, with the same data type as the sequencing column.

      Covered in Type 2 rules: selective history, truncates and schemas

    Practise it for real

    Run the documentation's sample change feed through an SCD Type 1 and an SCD Type 2 AUTO CDC flow and compare the two targets.

    1. 1.From your pipeline's exploration folder (not the transformations folder), run the sample-data SQL that creates main.cdc_tutorial.users_cdf with userId, name, city, operation and sequenceNum.

      Why: The sample data is not meant to be part of the pipeline definition. It is the CDC feed the flows read from.

      You should see: A table with eight change rows: inserts at sequence 1–2, a DELETE for 123 at 6, and updates at 5 and 6 with the sequence-5 rows listed last.

    2. 2.In the transformations folder, add the SCD Type 1 Python code: the users view, create_streaming_table("users_current"), and create_auto_cdc_flow with stored_as_scd_type = 1. Run the pipeline.

      Why: This shows the default upsert behaviour and how out-of-order events are resolved by sequenceNum.

      You should see: users_current holds three rows: 124 Oaxaca, 125 Guadalajara, 126 Cancun.

    3. 3.Add a second target, users_history, with create_auto_cdc_flow and stored_as_scd_type = "2", leaving out apply_as_truncates. Run the pipeline again.

      Why: Type 2 keeps every version, and truncate handling is not supported for it.

      You should see: users_history holds seven rows with __START_AT and __END_AT. 124, 125/Guadalajara and 126 have __END_AT null, and user 123 has two closed versions.

    4. 4.Query users_history filtered on __END_AT IS NULL and compare the result with users_current.

      Why: The current slice of a Type 2 table should match the Type 1 table for the same feed.

      You should see: The same three users and cities as users_current.

    Stuck? Get a nudge

    If the pipeline rejects the Type 2 flow, check that you didn't copy apply_as_truncates over from the Type 1 version.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Set to 1 for SCD type 1, 2 for SCD type 2”
      ↩︎ Switching a flow to SCD Type 2
      “The apply_as_truncates parameter is supported only for SCD type 1. SCD type 2 does not support truncate operations.”
      ↩︎ Type 2 rules: selective history, truncates and schemas
      “Bitemporal requires system_sequence_by and is in Beta.”
      ↩︎ Type 2 rules: selective history, truncates and schemas
      “you must include the __START_AT and __END_AT columns with the same data type as the sequence_by fields.”
      ↩︎ Exam trap 3
    2. 2.
      “Use SCD Type 2 to retain a history of records, either on all updates or on updates to a specified set of columns.”
      ↩︎ Switching a flow to SCD Type 2
      “with __START_AT and __END_AT columns indicating when each version was active.”
      ↩︎ Switching a flow to SCD Type 2
      “Records with __END_AT = null are currently active.”
      ↩︎ Reading the history: __START_AT, __END_AT and deletes
      “User 123 has two versions (ended at sequence 6 when deleted).”
      ↩︎ Exam trap 1
      “User 123 has two versions (ended at sequence 6 when deleted).”
      ↩︎ Prediction
    3. 4.
      “The default is to track history for all the output columns when there are any changes, equivalent to TRACK HISTORY ON *.”
      ↩︎ Type 2 rules: selective history, truncates and schemas
      “For SCD type 2 tables, when specifying the schema of the target table, you must also include the __START_AT and __END_AT columns”
      ↩︎ Type 2 rules: selective history, truncates and schemas
      “The APPLY AS TRUNCATE WHEN clause is supported only for SCD type 1. SCD type 2 does not support the truncate operation.”
      ↩︎ Exam trap 2

    Ready to test yourself?

    Practise Databricks Certified Data Engineer Professional in quiz mode.

    Spotted a mistake, or was something unclear? Tell us.