数仓SCD

发布时间:2026/8/22 1:27:11
数仓SCD SCD 在数仓里通常指Slowly Changing Dimension缓慢变化维中文叫缓慢变化维。它解决的问题是维度表中的属性发生变化时历史数据要不要保留以及如何保留。例如客户原来属于“华东区”后来调整为“华南区”。如果直接更新客户维表那么历史订单再关联客户时可能也会显示成“华南区”导致历史口径被改写。一、为什么需要 SCD假设有客户维表customer_idcustomer_nameregion1001张三华东2024 年张三在华东下了一笔订单。2025 年张三被划分到华南。如果直接把维表更新为customer_idcustomer_nameregion1001张三华南那么查询 2024 年订单时可能会得到张三华南区2024 年订单金额……但当时张三实际属于华东这就是历史失真。SCD 的核心就是决定是否覆盖旧值是否新增一条记录是否保存部分历史历史事实应该关联哪个版本的维度。二、常见 SCD 类型SCD Type 0不处理变化属性一旦写入就不再修改。例如客户出生日期、身份证号等通常可以采用 Type 0。原始值customer_idbirthday10011990-01-01即使源系统后来传来不同日期也不更新。特点保留最初值实现最简单不适合需要修正或追踪变化的字段。SCD Type 1直接覆盖发生变化时直接更新原记录不保留历史。原来customer_idregion1001华东更新后customer_idregion1001华南适用场景不关心历史只需要当前最新状态数据纠错姓名、电话、邮箱等只看当前值的字段。优点逻辑简单维表数据量小查询方便。缺点历史值丢失无法还原某个时间点的维度状态。SCD Type 2新增版本记录发生变化时不更新旧记录而是关闭旧版本再新增一条当前版本。这是数仓中最常见、最完整的历史追踪方式。维表结构一般会增加代理键customer_sk业务键customer_id生效时间start_date失效时间end_date当前标识is_current版本号version例如customer_skcustomer_idregionstart_dateend_dateis_current11001华东2024-01-012025-03-01021001华南2025-03-019999-12-311发生变化时的处理原记录customer_id 1001 region 华东 is_current 1变化为华南后将原记录更新为历史版本end_date 2025-03-01 is_current 0新增当前版本region 华南 start_date 2025-03-01 end_date 9999-12-31 is_current 1查询当前版本SELECT*FROMdim_customerWHEREis_current1;查询某天有效的版本SELECT*FROMdim_customerWHEREcustomer_id1001AND2024-08-01start_dateAND2024-08-01end_date;优点完整保留历史可以进行时点查询支持历史报表准确还原。缺点维度表会产生多条同一业务对象记录事实表通常需要关联代理键ETL 逻辑相对复杂。SCD Type 3增加历史字段Type 3 不新增记录而是在同一行中增加“当前值”和“历史值”。例如customer_idcurrent_regionprevious_region1001华南华东也可以设计成customer_idregionprevious_regionchange_date1001华南华东2025-03-01适用场景只需要保存有限的历史例如当前部门上一个部门当前销售经理上一个销售经理。优点查询简单不会产生多版本记录。缺点只能保存有限历史多次变化会覆盖更早的历史不适合完整审计。SCD Type 4历史表与当前表分离将当前数据放在当前维表历史版本放在历史表。例如当前表dim_customercustomer_idregionupdate_time1001华南2025-03-01历史表dim_customer_historycustomer_idregionstart_dateend_date1001华东2024-01-012025-03-01适用场景当前查询非常频繁历史数据量很大希望当前表保持简单当前数据与历史数据使用场景差异明显。SCD Type 6Type 1 Type 2 Type 3Type 6 是一种混合模式通常同时使用Type 1覆盖某些字段Type 2新增版本保留历史Type 3保留上一个值。例如customer_skcustomer_idregioncurrent_regionprevious_regionstart_dateend_dateis_current11001华东华南NULL2024-01-012025-03-01021001华南华南华东2025-03-019999-12-311这是比较复杂的设计只有在报表既需要完整历史又需要快速获取当前/上一次状态时才使用。三、实际项目中最常用的是 Type 1 和 Type 2通常可以按字段分别设计。例如客户维表字段处理方式原因客户姓名Type 1只需要最新姓名手机号Type 1只需要当前联系方式客户等级Type 2需要分析历史等级所属区域Type 2需要还原历史归属注册日期Type 0一般不应变化所以不是一张维表只能使用一种 SCD 类型而是不同字段可以使用不同的变化处理策略。四、Type 2 的典型表结构推荐结构如下CREATETABLEdim_customer(customer_skBIGINT,customer_id STRING,customer_name STRING,region STRING,levelSTRING,start_dateDATE,end_dateDATE,is_currentINT,versionINT,etl_timeTIMESTAMP);其中customer_id业务系统中的客户编号customer_sk数仓生成的代理键start_date该版本开始生效时间end_date该版本结束生效时间is_current是否当前版本version版本号。五、Type 2 的处理流程假设源数据每天同步到ods_customer目标维表为dim_customer处理步骤通常是1. 找出新增客户源表中有维表中没有source.customer_idISNOTNULLANDtarget.customer_idISNULL直接插入新版本。2. 找出属性发生变化的客户例如比较区域和等级source.regiontarget.regionORsource.leveltarget.level注意要处理 NULLCOALESCE(source.region,)COALESCE(target.region,)3. 关闭旧版本UPDATEdim_customerSETend_dateCURRENT_DATE,is_current0WHEREcustomer_id1001ANDis_current1;4. 插入新版本INSERTINTOdim_customer(customer_sk,customer_id,customer_name,region,level,start_date,end_date,is_current,version)VALUES(2002,1001,张三,华南,VIP,CURRENT_DATE,DATE9999-12-31,1,2);5. 没有变化的客户不做任何操作继续保留当前版本。六、事实表如何关联 SCD Type 2 维表这是很多人最容易混淆的地方。假设订单发生时间为order_date关联条件不能只写业务键ONfact.customer_iddim.customer_id因为同一个客户可能对应多个维度版本。应该使用业务键加时间范围SELECTf.order_id,f.order_date,f.amount,d.customer_sk,d.regionFROMfact_order fJOINdim_customer dONf.customer_idd.customer_idANDf.order_dated.start_dateANDf.order_dated.end_date;这样2024 年订单关联到“华东”版本2025 年订单关联到“华南”版本。如果事实表在装载时就已经拿到了正确的customer_sk后续查询可以直接关联ONfact.customer_skdim.customer_sk七、代理键为什么重要业务键可能不变例如customer_id 1001但客户的维度版本会变化因此需要代理键区分版本customer_skcustomer_idregion11001华东21001华南事实表保存的是customer_sk 1还是customer_sk 2取决于订单发生时客户属于哪个版本。所以业务键识别“是谁”代理键识别“这个人在当时对应哪个维度版本”。八、SCD 与拉链表在国内数仓实践中SCD Type 2 经常被称为拉链表。典型字段start_dt end_dt例如customer_idregionstart_dtend_dt1001华东2024-01-012025-03-011001华南2025-03-019999-12-31这里的时间范围通常采用[start_dt, end_dt)也就是start_dt查询时间AND查询时间end_dt采用左闭右开可以避免边界日期重复。九、SCD 设计时的几个关键问题1. 是按业务时间还是处理时间业务时间客户实际变更的时间处理时间数仓收到或处理变更的时间。如果源系统提供有效时间优先使用业务时间。如果只有每日快照则通常使用 ETL 日期作为版本时间。2. 一天内变化多次怎么办如果一天内客户从华东变为华南又变为华北按天粒度可能无法完整记录。可以使用秒级时间戳CDC 变更日志业务系统的变更时间更高频率同步。3. 删除如何处理常见做法逻辑删除is_deleted 1或者关闭当前版本end_date 删除时间 is_current 0物理删除直接删除维度记录。一般不建议因为会破坏历史分析。4. 数据修正怎么办如果是历史错误修正需要区分两类纠正当前值可以 Type 1 覆盖纠正历史版本需要回溯修改对应历史记录并可能重新装载事实表。十、简单总结类型处理方式是否保留完整历史常见用途Type 0不更新保留初始值出生日期、注册日期Type 1直接覆盖否姓名、电话Type 2新增版本是区域、等级、组织Type 3增加历史字段部分当前值和上一次值Type 4当前表历史表是当前与历史分离Type 6混合使用是复杂分析场景最重要的一句话是SCD 的本质是在维度属性变化时决定“覆盖旧值”还是“保留历史版本”。实际数仓中最常用的组合是Type 1 处理不关心历史的字段Type 2 处理需要追踪历史的字段。