SCD 类型1:覆盖——最简单,也最容易被误用

SCD 类型1,说白了就是「直接覆盖」。新数据来了,旧数据直接丢掉,不留任何历史痕迹。这是所有缓慢变化维策略里最粗暴、也最省事的一种。

我个人习惯把它叫做「失忆模式」。为什么?因为一旦覆盖,过去的状态就再也查不回来了。你想想看,这在很多业务场景下其实挺危险的。但话说回来,有些场景它确实是最优解。

核心定义:当维度属性发生变化时,直接用新值覆盖旧值,不保留任何历史版本。

实现方式:就一条 UPDATE 语句的事

实现起来真的没什么技术含量。一条 UPDATE,搞定。我见过不少刚入行的同学,觉得这太简单了,反而会想复杂。其实没必要。

-- 假设我们有一个客户维度表 dim_customer
-- 客户ID = 1001 的邮箱地址需要修正

UPDATE dim_customer
SET email = 'zhang.san@newcompany.com',
    update_time = CURRENT_TIMESTAMP
WHERE customer_id = 1001;

嗯,就这么简单。但要注意一点——一定要记录更新时间戳。虽然你丢了历史值,但至少知道「什么时候改的」。我在项目中吃过这个亏,后面会讲到。

适用场景:什么时候该用「失忆模式」?

类型1不是万能的,但有几个场景它特别合适。

场景 说明 我的经验
错误修正 数据录入错误,比如电话号码少了一位、邮箱拼写错了 这是最常见的场景,我几乎每周都会遇到
非关键属性 对分析没有历史意义的字段,比如客户昵称、临时备注 别在这种字段上浪费 SCD 类型2 的存储
合规擦除 GDPR 等法规要求删除或更新个人敏感信息 这时候你巴不得没有历史记录
源系统无历史 源系统本身就不保留变更历史,你也没办法 巧妇难为无米之炊,类型1是唯一选择

我的建议:如果你不确定某个字段该不该用类型1,先问自己一个问题——「三个月后,有人需要查这个字段之前的值吗?」如果答案是「不需要」,那就放心用覆盖。

优缺点分析:别只看它简单

类型1的优点很明显,但坑也不少。我一个个说。

优点

  • 实现成本极低——一条 SQL,不需要额外的表、不需要拉链表、不需要有效期字段
  • 查询性能最好——没有 JOIN 历史表的开销,也不需要过滤有效记录
  • 存储最省——只保留一份数据,不会像类型2那样膨胀
  • ETL 逻辑简单——MERGE 语句里的 UPDATE 分支,几行代码搞定

缺点

  • 历史信息彻底丢失——这是最大的问题。一旦覆盖,再也回不去
  • 破坏审计链——如果你需要做数据审计,类型1会让你欲哭无泪
  • 报表结果会变——昨天的报表和今天的报表,同一个维度可能算出不同的结果
  • 无法回溯分析——比如你想分析「客户在修改邮箱前的行为」,做不到

我曾经踩过的坑:有一次做客户生命周期分析,用了类型1覆盖了客户的「会员等级」字段。结果跑出来的分析结果完全对不上——因为半年前的老客户,等级全被最新的值覆盖了。那次之后,凡是跟「时间」相关的维度属性,我都不敢轻易用类型1。

SQL 示例:完整的 MERGE 实现

实际生产中,我们不会只写一条 UPDATE。通常会放在 MERGE 语句里,跟新增数据一起处理。下面是一个典型的实现。

-- 假设源表为 src_customer,目标表为 dim_customer
-- 我们只对 email 字段做类型1覆盖

MERGE INTO dim_customer AS target
USING src_customer AS source
ON target.customer_id = source.customer_id

WHEN MATCHED AND (
    target.email != source.email
    OR target.phone != source.phone
) THEN
    UPDATE SET
        email = source.email,
        phone = source.phone,
        update_time = CURRENT_TIMESTAMP

WHEN NOT MATCHED THEN
    INSERT (
        customer_id,
        name,
        email,
        phone,
        create_time,
        update_time
    ) VALUES (
        source.customer_id,
        source.name,
        source.email,
        source.phone,
        CURRENT_TIMESTAMP,
        CURRENT_TIMESTAMP
    );

注意看 WHEN MATCHED AND 后面的条件——我只在值真正变化时才更新。为什么?因为不必要的更新会刷掉 update_time,让你搞不清到底什么时候改的。这个细节,很多教程不会告诉你。

什么时候该放弃类型1?

我给自己定了一个原则,分享给你参考:

  • 如果这个字段的变更会影响历史报表的聚合结果 → 不要用类型1
  • 如果业务方明确要求「可回溯」 → 不要用类型1
  • 如果这个字段是核心分析维度(比如客户等级、产品分类) → 不要用类型1
  • 如果源系统会保留变更日志 → 优先考虑类型2

说白了,类型1是个「省心但不省事」的方案。省心的是实现,不省事的是后续你可能要面对业务方的灵魂拷问——「为什么上个月的数据跟现在不一样了?」

一句话总结:SCD 类型1适合「错了就改、改了不追」的场景。用之前,先想清楚你能不能承受「失忆」的代价。

SCD 类型1:覆盖决策流程 维度属性发生变化 是否需要保留历史版本? 是 否 SCD 类型2 考虑类型1 是错误修正吗? 是 否 类型1 评估类型2/3 执行 UPDATE 覆盖 核心原则:只有「改了也不影响历史分析」的属性,才适合用类型1覆盖 常见场景:错误修正、非关键属性、合规擦除、源系统无历史