首页/数据/数仓建模速查/SCD2 关闭旧版本行的正确条件
维度与事实设计149 条

SCD2 关闭旧版本行的正确条件

关闭条件必须同时锁定「自然键 + 当前版本」两个条件:缺少 is_current 过滤会把历史行一起重写,造成区间错乱。

数仓建模速查数据更新于 2026-10-13

关闭旧版本的条件

关闭旧版本行的条件必须同时锁定「自然键」与「当前版本」两项:只按自然键更新,会把该实体的全部历史行的 end_date 与当前标志一起改写,版本区间整体损毁;漏掉当前标志这个条件,事后几乎无法从数据看出问题。正确写法是 WHERE 自然键 IN 变更集合 AND is_current = 1,且只更新 end_date 与当前标志两列,执行顺序为先关闭再插入。

基本信息

锁定列自然键 + is_current = 1
可更新列end_date 与当前标志,其他列一律不动
执行顺序先关闭旧行,后插入新行
幂等保护重跑时按批次判断,避免 end_date 被后一批覆盖
引擎差异列式引擎大范围 UPDATE 代价高,常见做法是重写受影响分区

典型使用场景

  • SCD2 增量作业:每个批次都要关闭变更成员的上一个版本
  • 失败重跑:作业失败后重跑,关闭逻辑必须幂等,不能把 end_date 改乱
  • 存量修复:发现历史区间被误改,按变更明细重新计算 end_date
  • 迁移改造:从全量重算迁移到增量关闭,需保证区间结果一致
  • 分区重写优化:在 Hive 或 Spark 上改用重写受影响分区替代 UPDATE
  • 区间校验:关闭完成后立即校验每个自然键的区间连续且不重叠

关闭旧版本与校验

关闭旧版本与校验
-- 正确:自然键 + 当前标志,缺一不可
UPDATE dim_customer
SET    end_date = :batch_dt,   -- 关闭日,通常取批次日期减一天
       is_current = 0
WHERE  customer_id IN (SELECT customer_id FROM changed_keys)
  AND  is_current = 1;

-- 幂等保护:只关闭还没被处理的当前行
-- AND NOT EXISTS (SELECT 1 FROM dim_customer_hist_audit a
--                 WHERE a.customer_id = dim_customer.customer_id
--                   AND a.batch_id = :batch_id)

-- 关闭后校验:同一自然键是否仍有零条或多条当前行
SELECT customer_id, sum(case when is_current = 1 then 1 else 0 end) AS cur_cnt
FROM   dim_customer GROUP BY customer_id HAVING cur_cnt <> 1;

写法对照

写法后果
WHERE 自然键 IN (变更集)全历史行被一起关闭,区间信息整体损毁
WHERE 自然键 IN (变更集) AND is_current = 1正确,只影响当前版本行
只按最大版本号关闭变更集与版本号不一致时漏关,出现两条当前行
关闭与插入不分批次中途失败留下零条当前行,报表取不到属性
重跑不判批次end_date 被重复覆盖,区间漂移
在 Hive、Spark 这类以文件重写为代价的引擎上,大规模 UPDATE 会重写整张表或整分区,代价远超预期。常见替代方案是:识别受影响自然键所在分区,重写这些分区,再用原子替换落库;评估时要连着下游读取一起排期。

易错点

  • 忘记 is_current 过滤:整条版本链的 end_date 与当前标志被改写,历史区间永久损毁
  • 重跑无幂等保护:end_date 被后一批次覆盖,区间一天天往后漂
  • 关闭与插入不在同一事务:失败后同一自然键零条当前行,报表取不到属性
  • 在列式引擎上直接大范围 UPDATE:全表重写导致调度超时,连带影响其他作业
  • 关闭后不做区间校验:重叠与断档流入生产,等到按区间点查时才暴露

数据来源与参考

  • 数据仓库工具箱:维度版本管理
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌