首页/数据/数据工程与 ETL 速查/缓慢变化维 Type 2:版本化保留历史

缓慢变化维 Type 2:版本化保留历史

Type 2 在属性变化时关闭当前版本、插入新版本行,用生效起止区间区分;事实表引用当时的代理键,因此历史指标可以按当时口径复现,是数仓常用的历史保留方式。

Type 2 怎么做版本化

Type 2 在属性发生变化时,先把当前版本关链(写入结束日期、把当前标志置为否),再插入一条新版本行(新的代理键、新的生效起始日期)。事实表写入时取事件发生时刻生效的那个代理键,因此历史指标可以按当时口径复现。这是数仓保留历史最常用的方式,代价是同一实体在维度表中会有多行。

基本信息

字段代理键、业务键、属性、start_date、end_date、is_current
变化处理旧行关链,同时新增一行
事实关联写入时取事件时间下生效的代理键
能力支持按历史属性回溯分析

典型使用场景

  • 会员等级变化:保留每次升级记录,支持按当时等级统计
  • 组织归属调整:历史业绩按当时所属组织归集
  • 商品类目调整:类目变化后历史销量仍归到旧类目
  • 补历史数据:按事件时间匹配当时生效的版本
  • 维度治理:只对需要回溯的属性启用版本化

两段式维护

两段式维护
-- 1) 关闭发生变化的当前版本
UPDATE dim_user SET end_date = DATE '2026-10-11', is_current = false
WHERE user_id IN (SELECT user_id FROM stg_user_changed) AND is_current = true;

-- 2) 插入新版本行
INSERT INTO dim_user (user_sk, user_id, user_name, city, level, start_date, end_date, is_current)
SELECT seq_user_sk.NEXTVAL, user_id, user_name, city, level,
       DATE '2026-10-11', DATE '9999-12-31', true
FROM stg_user_changed;

版本行字段读法

字段含义注意点
user_sk代理键,每个版本不同事实表关联的就是它
user_id业务键,同一实体不变用于找出同一实体的所有版本
start_date本版本生效日期取区间时按左闭处理
end_date本版本失效日期当前版本写极限日期
is_current是否当前版本辅助标记,不能替代区间条件
事实表写入时要按事件时间匹配生效中的维度版本,而不是简单取 is_current = true,否则补历史数据会取到最新版本,历史口径被污染。
end_date 用极限日期和用 NULL 混用,会让关联条件写法不一致:同一套 SQL 在不同表上结果不同,排查时极难发现。

版本化带来的成本

Type 2 的代价要说清楚:同一实体在维度表里会有多行,维度表行数随变更次数线性增长,关联时如果只按业务键 join 会产生笛卡尔放大。所以事实表必须存代理键、关联时按代理键等值 join;只有需要按时间区间回溯时才用业务键加区间条件。另一个常见问题是变更判定:只比较业务属性列,把更新时间之类的技术字段排除在外,否则每次同步都会被判定为「发生变化」,维度版本会无意义地膨胀。

易错点

  • 补历史分区时用当前版本维度回刷,历史口径被污染
  • end_date 用极限日期与用 NULL 混用,关联条件写法不一致
  • 关链与新增不在同一事务中,中途失败留下没有当前版本的实体
  • 关了链却忘记插入新版本,业务查询找不到该实体的当前状态

数据来源与参考

  • Kimball《The Data Warehouse Toolkit》SCD Type 2
  • 行业数仓拉链表实践
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌