SCD 类型0:保留原始值——最“懒”也最稳的策略
说实话,SCD 类型0 是所有缓慢变化维策略里最没“技术含量”的一个。但你别小看它。我在好几个项目里都见过有人把不该变的字段给覆盖了,结果数据对不上,业务方追着问“为什么用户注册日期变了?”——那场面,挺尴尬的。
类型0 的核心就一句话:写入后永不修改。不管源系统怎么变,不管业务状态怎么更新,这个字段的值一旦落进数仓,就永远定格在那一刻。
类型0 定义:维度属性在首次加载后,无论源系统发生任何变化,目标表都保持原始值不变。相当于给数据拍了一张“出生照”。
实现方式:简单到不需要动脑子
实现类型0 的方式,说白了就是“不处理”。你只需要在 ETL 里写一条规则:首次加载写入,后续更新忽略该字段。
我习惯用两种方式来实现:
方式一:全量快照 + 固定字段
每次全量拉取时,只对非类型0 字段做覆盖,类型0 字段保持首次值。举个例子:
-- 假设用户维度表,birth_date 和 reg_time 是类型0 字段
-- 首次加载
INSERT INTO dim_user (user_id, user_name, birth_date, reg_time, city)
SELECT user_id, user_name, birth_date, reg_time, city
FROM source_user;
-- 后续更新:只更新非类型0 字段
UPDATE dim_user t
SET t.user_name = s.user_name,
t.city = s.city
FROM source_user s
WHERE t.user_id = s.user_id;
-- birth_date 和 reg_time 不动
方式二:增量合并 + 条件判断
用 MERGE 语句时,对类型0 字段做“仅插入不更新”处理:
MERGE INTO dim_user t
USING source_user s
ON t.user_id = s.user_id
WHEN MATCHED THEN
UPDATE SET
t.user_name = s.user_name,
t.city = s.city
-- 注意:birth_date 和 reg_time 不在 UPDATE 子句中
WHEN NOT MATCHED THEN
INSERT (user_id, user_name, birth_date, reg_time, city)
VALUES (s.user_id, s.user_name, s.birth_date, s.reg_time, s.city);
我的小习惯:在建模文档里,我会给类型0 字段加一个特殊标记,比如字段注释里写上 【SCD0】。这样后来接手的人一眼就能看出来——这个字段不能动。
适用场景:哪些字段该“冻住”?
你想想看,什么样的数据一旦产生就不该再变了?我总结了三个典型场景:
| 场景 | 典型字段 | 为什么用类型0 |
|---|---|---|
| 生物特征 | 出生日期、性别(生理)、血型 | 这些是客观事实,不会因为业务操作而改变 |
| 注册/创建时间 | 注册时间、首次下单时间、账号创建时间 | 时间戳一旦记录,回溯历史时不能篡改 |
| 业务主键 | 身份证号、手机号(作为业务标识时) | 主键变了意味着实体变了,不是同一个对象了 |
| 历史快照标识 | 首次渠道来源、首次推荐人 | 分析“首次”行为时,这些值必须固定 |
我在做电商数仓时遇到过这样一个需求:运营要分析“用户注册当年的购买转化率”。如果注册时间被后续更新覆盖了,那这个分析就全乱了。所以注册时间我直接标了类型0,谁也别想动它。
优缺点分析:稳,但死板
类型0 的好处很明显,坏处也很明显。咱们摊开来说:
优点
- 实现成本极低:不需要拉链表,不需要生效时间,不需要版本号。写几行 SQL 就搞定。
- 查询性能最优:没有历史版本,没有分区膨胀,一张表搞定所有。查询时不需要考虑“取哪个版本”。
- 数据一致性最强:永远不会出现“同一个用户两个出生日期”的怪事。审计时也最干净。
- ETL 逻辑最简:不需要判断变化,不需要生成新版本,出错概率最低。
缺点
- 丢失历史变化:如果某个字段其实会变(比如用户改了手机号),类型0 就记录不了这个变化。
- 与源系统脱节:源系统更新了,数仓不跟着变,时间长了两边数据对不上。
- 适用范围窄:真正“永不变化”的字段其实很少。大部分业务属性都会变。
- 容易误用:我见过有人把“用户等级”也设成类型0,结果分析时发现用户等级永远是最初的——那分析还有什么意义?
我曾经踩过的坑:有一次我把用户的“所在城市”设成了类型0,想着“用户注册时填的城市应该不变吧”。结果半年后业务方要做地域分析,发现大量用户已经搬家了,但数仓里还是老城市。最后不得不重刷历史数据,折腾了两天。所以——类型0 只适用于真正不会变的属性,别凭感觉拍脑袋。
一张图看懂类型0
下面这张图展示了类型0 的核心逻辑:数据一旦写入,就永远定格。源系统后续的变化,跟数仓没关系了。
什么时候该用类型0?
我给自己定了个判断标准,分享给你参考:
- 这个字段在业务上有没有“变”的合理性?——出生日期变了?不可能。手机号变了?有可能。先问自己这个问题。
- 如果变了,分析场景是否需要追溯历史?——比如“注册时的城市”和“当前的城市”是两个不同的分析维度。前者用类型0,后者用类型1或类型2。
- 这个字段是不是业务主键的一部分?——如果是,那必须类型0。主键变了就是另一个实体了。
- 有没有监管或审计要求?——金融行业里,交易时间、客户首次登记信息等,监管要求不得修改。这种场景类型0 是唯一选择。
我的建议:建模初期,把所有字段过一遍,把明显不会变的先标成类型0。剩下的再按业务需求选类型1、类型2 或类型3。这样能减少后续的维护成本。但记住——宁可少标,不要多标。标错了类型0,后面改起来很麻烦。