目录
前言
维度表属性随着时间而变化,好的数据模型应具有追踪维度变化的能力。Kinball在维度建模工具箱一书中提出7种缓慢变化维度(Slowly changing Dimension, SCD)处理技术。本文逐一详解7种设计方法,其中类型1-4为基本设计方法,类型5-7由类型1-4相互组合形成。
维度属性不变的情况,在Kinball理论中成为类型0情况,如一个人的出生日期,这类属性一旦有值,将不在变化,可以视为常量。
为辅助讲解,本文设定一个案例场景:为统计不同员工的计算资源消耗情况,存在一张员工维度表和一张任务计算事实表。
其中员工基本属性:
代理键 | 员工编号 | 姓名 | username | 部门名称 | ··· |
事实表属性:
任务id | 执行人 | 资源消耗 | ··· |
员工的部门可能改变,接下来讨论员工部门改变后,数据如何处理。
基本设计方法
类型1:重写
针对变化的属性,直接重写原有的值。
例如,张三原部门为“数据开发1组”,在2021年1月1日部门变动为“数据开发2组”,对应数据的调整为:
改变前:
代理键 | 员工编号 | 姓名 | username | 部门名称 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 |
改变后:
代理键 | 员工编号 | 姓名 | username | 部门名称 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发2组 |
新的部门值覆盖原有的值,代理键不变,事实表关联同一个键值。
这种实现方式无论ETL操作还是业务使用均很简单,但是缺点也很明显,无法追踪数据的历史状态。如分析人员希望1月1号之前的数据统计在“数据开发1组”部门中,则无法实现。
同时对于下游BI应用或汇总表,维度变化后需要重刷下游,否则进行上卷/下钻操作时可能出现数据不一致情况。
类型2:增加新行
针对每个变化的属性,增加一个新行。也被称为拉链表,在业内广泛使用。
属性变化时,保留原有记录同时使用最新值新增一条记录。维表设计时需额外增加几列,分别表示当前记录的生效起始时间、生效结束时间和是否当前行标识。
改变前:
代理键 | 员工编号 | 姓名 | username | 部门名称 | 行生效日期 | 行失效日期 | 是否当前行 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 | 2020-09-01 | 9999-99-99 | 是 |
改变后:
代理键 | 员工编号 | 姓名 | username | 部门名称 | 行生效日期 | 行失效日期 | 是否当前行 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 | 2020-09-01 | 2020-12-31 | 否 |
1035 | NO001 | 张三 | zhangsan1 | 数据开发2组 | 2021-01-01 | 9999-99-99 | 是 |
1月1号员工部门变更后,维度表新增键值为1035的一行记录,行生效日期为1月1号,同时标识为当前行。原有的1009行将行失效日期修改为2020-12-31,修改是否当前行为“否”表示当前行已经不是最新的记录。
对于事实表,在1月1号之前发生的事实关联1009,1月1号之后的事实关联1035。在统计时,1月1号之前的事实通过1009关联将被计算在“开发1组”,1月1号之后的事实通过1035被计算在“开发2组”,满足保存历史变化的分析需求。
该方式易于理解,ETL也不复杂,能满足多数的需求场景。
缺点是无法轻易满足按维度当前状态统计历史事实的需求。如分析人员提出临时要求,员工所属部门不再区分历史变化,统一按照最新的部门统计事实。类型2无法轻易实现。虽然可以通过维度表查出员工编号进一步查找该员工编号的当前行,总体实现仍比较麻烦。
此外,由于每次属性变化均新增一行,可能导致维度数据量快速增长。
类型3:增加新属性
该方式下,针对每次变化增加一列属性,用以保存旧属性值。
改变前:
代理键 | 员工编号 | 姓名 | username | 当前部门名称 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 |
改变后:
代理键 | 员工编号 | 姓名 | username | 当前部门名称 | 原部门名称 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发2组 | 数据开发1组 |
在这种方式下,属性每变化一次,新增一列保存原属性值。
类型3适应于未来变化可预测的,比如每年固定变一次,同时涉及表中大量记录变化的情况。在这种情况下,类型3有效减少新增记录数,同时较好支持访问历史状态的数据。
该方式适应场景较少,没有类型1和2使用的广泛。
类型4:增加微型维度
类型2中,如果维度表数量很大,同时有几个属性频繁更新,就可能导致维度表爆炸性增长,进而影响性能。将频繁变化的维度从维度表中去除,另存一张维度表,这张维度表称为“微型维度”,原维度表称为“主维度表”。
拆分之后,主表数据量不再剧增。同时,经过良好的设计,微型维度的数量保持稳定,满足性能的需求。比如,用户属性有年龄、学历、收入、活跃度等。如果将这部分信息放在主表中,每个属性变更都将在主表中增加一行。
微型维表通过将收入划分为范围,如0-5000、5000-10000等,以及活跃度高、中、低等,存储所有年龄、学历、收入和活跃度的组合信息,初始化后表的记录即稳定不再增长,并且数据量控制在一定范围内。由于收入在微型维度中以范围方式存储,如果有访问准确数值的需求,可以将准确数值存入事实表中。
如:
事实表关联的用户统计信息是事实发生的那一刻用户的统计信息,不同时间段事实表关联的同一用户的用户统计信息不同。这种方式下,支持根据历史变化进行统计,比如,能够统计出在同一用户在收入不同阶段的购买偏好。
该方式使用的关键是恰当设计微型维度表,最好是将值可以枚举或可以划分为范围的属性加入微型维度表中,以避免表数据量过大。
相对于类型2,能够避免维度表的爆炸增长。但是需要设计者对微型维度表恰当设计,具备较高模型建设能力。另外由于事实表关联多个用户信息外键,可能给使用人员带来困惑。
此外,和类型2类似,无法轻易满足按照用户最新状态统计过往事实的需求。
混合设计方法
类型5:类型4与类型1组合
类型5在类型4的基础上,通过主维度表增加对用户统计信息表的关联,每次用户统计信息变更,采用类型1方式更新该关联键值。在Kinball理论中,此时微型维度表也称为支架表。
如:
这种方式下,事实表关联的用户统计信息是事实发生时刻的事实,主表关联的用户统计信息采用类型1更新,始终保存当前最新的状态。
通过事实表关联的外键可以追踪用户统计信息历史变化,通过主维度表关联的统计信息支持按当前状态访问历史事实。
但是由于按当前状态访问时需先关联主维度表再关联用户统计信息表,对性能有影响。另外由于事实表和主维度表均包含用户统计信息,容易造成分析人员误用。
类型6:类型1和类型2组合
之前提过,类型2的缺点:当需要根据用户当前状态统计过往事实时,难以实现。比如希望根据用户当前部门“数据开发2组”统计用户所有的事实,需要先通过1009找到用户编号No0001,再从维表中找出该编号当前生效的key的部门,即1035行的“数据开发2组”。
类型6为解决该问题,在维度表中增加一列,该列始终存储该属性的当前最新值,即以类型1方式更新该属性。
如,改变前:
代理键 | 员工编号 | 姓名 | username | 部门名称 | 最新部门名称 | 行生效日期 | 行失效日期 | 是否当前行 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 | 数据开发1组 | 2020-09-01 | 9999-99-99 | 是 |
改变后:
代理键 | 员工编号 | 姓名 | username | 部门名称 | 最新部门名称 | 行生效日期 | 行失效日期 | 是否当前行 |
1009 | NO001 | 张三 | zhangsan1 | 数据开发1组 | 数据开发2组 | 2020-09-01 | 2020-12-31 | 否 |
1035 | NO001 | 张三 | zhangsan1 | 数据开发2组 | 数据开发2组 | 2021-01-01 | 9999-99-99 | 是 |
每次属性变更后,除了做出类型2的改变,还会改变所有历史记录中“最新部门名称”的值为当前值。
类型6对类型2做了升级,解决了类型2的缺点。但是需额外增加一列,增加ETL成本。此外多个部门值也容易造成分析人员的误用。
类型7:类型1和类型2组合
类型6还有个问题,每个变化属性占2个字段,当属性较多时,导致维度字段爆炸。类型7为解决该问题,将“当前值”从维度表中移至事实表中。
该方法依赖于维度表存在持久键,比如上述案例中的“员工编号”,每个员工均不同。
设计案例:
员工表采用类型2设计,同时在新增事实表时将维表持久键即员工编号加入事实表中,然后创建一张通过员工编号关联员工当前属性的视图。当需要根据员工当前状态统计时,事实表通过员工编号关联视图即实现查询需求。
类型7和类型6解决相同问题,可以认为是类型6的进一步升级,以避免维度表属性过多。但是也存在和类型6同样的问题,即过多分析选项造成分析人员误用的可能。
结语
本文讨论了Kinball维度建模理论中,针对维度变化的7种设计方法。其中类型1-4为基本方法,5-7由基本类型混合组成。
类型1和2使用最广泛,能满足大多数场景需求,实现理念也比较容易理解。类型3在极少场景下适应,目前实际工作中还没碰到。如果能对维度表做恰当的划分,区分稳定的属性和经常变化的属性,同时经常变化的属性可以枚举或范围表示,类型4微型维度是最佳的选择。
类型5-7解决了类型2拉链表和类型4微型维度中,无法轻易按当前状态统计历史事实的问题。
如果类型1-4的设计能满足预期需求,就不要过于追求采用类型5-7的复杂方案。复杂方案会增加后期维护的难度,同时也会给分析人员增加误用的可能性。只有在确定既要历史变化又要最新状态的分析能力,再考虑类型5-7的设计方案。
在实际工作中,除了上述几种方法,还有一种更粗暴的解决办法,即将维度表每日全量存储一个分区,如果能接受这种方法下数据量的增长,也不失为一种可选方案。
附类型1-7的总结:
SCD类型 | 维度表影响 | 事实表影响 | 是否支持追踪历史变化 | 能否便捷实现按当前状态统计历史事实 |
类型1 | 重写属性值 | 维表变化无需修改事实表,事实表关联维表的当前值 | 否 | 是 |
类型2 | 为新属性值增加新行 | 维度变化无需修改事实表,事实表关联采用事实发生时维度的当前生效值 | 是 | 否 |
类型3 | 重写属性值并增加新列保存旧值 | 同类型1 | 是 | 是 |
类型4 | 为快速变化属性建立微型维度表 | 维度变化无需修改事实表,事实表同主维度表关联,并同微型维度表关联 | 是 | 否 |
类型5 | 采用类型4,并再主维表中采用类型1关联微型维度表 | 同类型4 | 是 | 是 |
类型6 | 采用类型2,并增加一列保存当前值 | 同类型2 | 是 | 是 |
类型7 | 采用类型2,将持久键加入事实表,并创建一张持久键的视图 | 维度变化无需修改事实表,事实表同主维度表关联,保存维度的持久键 | 是 | 是 |
相关推荐:
本文重度参考Kinball的《维度建模工具箱》第5章,购买链接:《大数据应用与技术丛书·数据仓库工具箱(第3版):维度建模权威指南》([美]Ralph,Kimball,[美]Margy,Ross)【摘要 书评 试读】- 京东图书