Skip to main content

Add snapshots to your DAG

What are snapshots?

Analysts often need to "look back in time" at previous data states in their mutable tables. While some source data systems are built in a way that makes accessing historical data possible, this is not always the case. dbt provides a mechanism, snapshots, which records changes to a mutable table over time.

Snapshots implement type-2 Slowly Changing Dimensions over mutable source tables. These Slowly Changing Dimensions (or SCDs) identify how a row in a table changes over time. Imagine you have an orders table where the status field can be overwritten as the order is processed.

idstatusupdated_at
1pending2024-01-01

Now, imagine that the order goes from "pending" to "shipped". That same record will now look like:

idstatusupdated_at
1shipped2024-01-02

This order is now in the "shipped" state, but we've lost the information about when the order was last in the "pending" state. This makes it difficult (or impossible) to analyze how long it took for an order to ship. dbt can "snapshot" these changes to help you understand how values in a row change over time. Here's an example of a snapshot table for the previous example:

idstatusupdated_atdbt_valid_fromdbt_valid_to
1pending2024-01-012024-01-012024-01-02
1shipped2024-01-022024-01-02null

Configuring snapshots

Configuration best practices

 Use the timestamp strategy where possible
 Use dbt_valid_to_current for easier date range queries
 Ensure your unique key is really unique

How snapshots work

When you run the dbt snapshot command:

  • On the first run: dbt will create the initial snapshot table — this will be the result set of your select statement, with additional columns including dbt_valid_from and dbt_valid_to. All records will have a dbt_valid_to = null or the value specified in dbt_valid_to_current (available in dbt Core 1.9+) if configured.
  • On subsequent runs: dbt will check which records have changed or if any new records have been created:
    • The dbt_valid_to column will be updated for any existing records that have changed.
    • The updated record and any new records will be inserted into the snapshot table. These records will now have dbt_valid_to = null or the value configured in dbt_valid_to_current (available in dbt Core v1.9+).

Snapshots can be referenced in downstream models the same way as referencing models — by using the ref function.

Detecting row changes

Snapshot "strategies" define how dbt knows if a row has changed. There are two strategies built-in to dbt:

  • Timestamp — Uses an updated_at column to determine if a row has changed.
  • Check — Compares a list of columns between their current and historical values to determine if a row has changed.

The timestamp strategy uses an updated_at field to determine if a row has changed. If the configured updated_at column for a row is more recent than the last time the snapshot ran, then dbt will invalidate the old record and record the new one. If the timestamps are unchanged, then dbt will not take any action.

The timestamp strategy requires the following configurations:

ConfigDescriptionExample
updated_atA column which represents when the source row was last updatedupdated_at

Example usage:

Check strategy

The check strategy is useful for tables which do not have a reliable updated_at column. This strategy works by comparing a list of columns between their current and historical values. If any of these columns have changed, then dbt will invalidate the old record and record the new one. If the column values are identical, then dbt will not take any action.

The check strategy requires the following configurations:

ConfigDescriptionExample
check_colsA list of columns to check for changes, or all to check all columns["name", "email"]
check_cols = 'all'

The check snapshot strategy can be configured to track changes to all columns by supplying check_cols = 'all'. It is better to explicitly enumerate the columns that you want to check. Consider using a surrogate key to condense many columns into a single column.

Example usage

Hard deletes (opt-in)

Snapshot meta-fields

Snapshot tables will be created as a clone of your source dataset, plus some additional meta-fields*.

In dbt Core v1.9+ (or available sooner in the "Latest" release track in dbt Cloud):

  • These column names can be customized to your team or organizational conventions using the snapshot_meta_column_names config. ess)
  • Use the dbt_valid_to_current config to set a custom indicator for the value of dbt_valid_to in current snapshot records (like a future date such as 9999-12-31). By default, this value is NULL. When set, dbt will use this specified value instead of NULL for dbt_valid_to for current records in the snapshot table.
  • Use the hard_deletes config to track deleted records as new rows with the dbt_is_deleted meta field when using the hard_deletes='new_record' field.
FieldMeaningUsage
dbt_valid_fromThe timestamp when this snapshot row was first insertedThis column can be used to order the different "versions" of a record.
dbt_valid_toThe timestamp when this row became invalidated.
For current records, this is NULL by default
The most recent snapshot record will have dbt_valid_to set to NULL
dbt_scd_idA unique key generated for each snapshotted record.This is used internally by dbt
dbt_updated_atThe updated_at timestamp of the source record when this snapshot row was inserted.This is used internally by dbt
dbt_is_deletedA boolean value indicating if the record has been deleted. True if deleted, False otherwise.Added when hard_deletes='new_record' is configured. This is used internally by dbt

*The timestamps used for each column are subtly different depending on the strategy you use:

For the timestamp strategy, the configured updated_at column is used to populate the dbt_valid_from, dbt_valid_to and dbt_updated_at columns.

Details for the timestamp strategy

Snapshot query results at 2024-01-01 11:00

idstatusupdated_at
1pending2024-01-01 10:47

Snapshot results (note that 11:00 is not used anywhere):

idstatusupdated_atdbt_valid_fromdbt_valid_todbt_updated_at
1pending2024-01-01 10:472024-01-01 10:472024-01-01 10:47

Query results at 2024-01-01 11:30:

idstatusupdated_at
1shipped2024-01-01 11:05

Snapshot results (note that 11:30 is not used anywhere):

idstatusupdated_atdbt_valid_fromdbt_valid_todbt_updated_at
1pending2024-01-01 10:472024-01-01 10:472024-01-01 11:052024-01-01 10:47
1shipped2024-01-01 11:052024-01-01 11:052024-01-01 11:05

Snapshot results with hard_deletes='new_record':

idstatusupdated_atdbt_valid_fromdbt_valid_todbt_updated_atdbt_is_deleted
1pending2024-01-01 10:472024-01-01 10:472024-01-01 11:052024-01-01 10:47False
1shipped2024-01-01 11:052024-01-01 11:052024-01-01 11:202024-01-01 11:05False
1deleted2024-01-01 11:202024-01-01 11:202024-01-01 11:20True

For the check strategy, the current timestamp is used to populate each column. If configured, the check strategy uses the updated_at column instead, as with the timestamp strategy.

Details for the check strategy

Snapshot query results at 2024-01-01 11:00

idstatus
1pending

Snapshot results:

idstatusdbt_valid_fromdbt_valid_todbt_updated_at
1pending2024-01-01 11:002024-01-01 11:00

Query results at 2024-01-01 11:30:

idstatus
1shipped

Snapshot results:

idstatusdbt_valid_fromdbt_valid_todbt_updated_at
1pending2024-01-01 11:002024-01-01 11:302024-01-01 11:00
1shipped2024-01-01 11:302024-01-01 11:30

Snapshot results with hard_deletes='new_record':

idstatusdbt_valid_fromdbt_valid_todbt_updated_atdbt_is_deleted
1pending2024-01-01 11:002024-01-01 11:302024-01-01 11:00False
1shipped2024-01-01 11:302024-01-01 11:402024-01-01 11:30False
1deleted2024-01-01 11:402024-01-01 11:40True

Configure snapshots in versions 1.8 and earlier

FAQs

How do I run one snapshot at a time?
How often should I run the snapshot command?
What happens if I add new columns to my snapshot query?
Do hooks run with snapshots?
Can I store my snapshots in a directory other than the `snapshot` directory in my project?
Debug Snapshot target is not a snapshot table errors
0