Slowly changing dimensions (SCD) help track changes in data over time. Type 2 keeps full history by adding new records when data changes.
Slowly changing dimensions (SCD Type 2) in dbt
with source_data as ( select * from {{ ref('your_source_table') }} ), new_records as ( select sd.id, sd.attribute, current_timestamp as effective_from, null as effective_to, true as is_current from source_data sd left join {{ this }} t on sd.id = t.id and t.is_current = true where t.id is null or sd.attribute <> t.attribute ), expired_records as ( select t.id, t.attribute, t.effective_from, current_timestamp as effective_to, false as is_current from {{ this }} t join source_data sd on t.id = sd.id where t.is_current = true and sd.attribute <> t.attribute ), final as ( select * from {{ this }} where is_current = false union all select * from expired_records union all select * from new_records union all select * from {{ this }} where is_current = true and id not in (select id from expired_records) ) select * from final
This example shows the main steps: find new or changed records, expire old ones, and keep current data.
Use {{ ref('table_name') }} to refer to source tables and {{ this }} for the current model.
select * from source_tableselect * from target_table where is_current = trueupdate target_table set effective_to = current_timestamp, is_current = false where id = '123' and is_current = true
insert into target_table (id, attribute, effective_from, effective_to, is_current) values ('123', 'new_value', current_timestamp, null, true)
This dbt model updates a customer dimension table using SCD Type 2 logic. It finds new or changed customers, expires old records, and inserts new current records.
with source_data as ( select * from {{ ref('customers_source') }} ), new_records as ( select sd.customer_id, sd.customer_name, current_timestamp as effective_from, null as effective_to, true as is_current from source_data sd left join {{ this }} t on sd.customer_id = t.customer_id and t.is_current = true where t.customer_id is null or sd.customer_name <> t.customer_name ), expired_records as ( select t.customer_id, t.customer_name, t.effective_from, current_timestamp as effective_to, false as is_current from {{ this }} t join source_data sd on t.customer_id = sd.customer_id where t.is_current = true and sd.customer_name <> t.customer_name ), final as ( select * from {{ this }} where is_current = false union all select * from expired_records union all select * from new_records union all select * from {{ this }} where is_current = true and customer_id not in (select customer_id from expired_records) ) select * from final
Always include effective_from and effective_to timestamps to track when records are valid.
Mark current records with a boolean flag like is_current for easy filtering.
Test your SCD logic on a small dataset before applying to large tables.
SCD Type 2 keeps full history by adding new rows for changes.
Use timestamps and flags to track current and past records.
dbt models can implement SCD Type 2 with SQL and incremental logic.