虽然我的主业是实时计算和批量计算,并不是数仓,但是在日常工作中绝对少不了与数仓打交道。并且我也算是参与过离线数仓建设的,维度建模的基础还是不能忘。本文就作为一篇抄书笔记吧。
SCD简介
顾名思义,缓慢变化维度(slowly changing dimension, SCD)就是数据仓库维度表中,那些随时间变化比较不明显,但仍然会发生变化的维度。考虑以下两个情境:
- 在员工维度表中,某员工原来在北京分公司工作,后来调往上海分公司,那么“工作地点”就是一个缓慢变化维度;
- 在采购维度表中,办公电脑原来从戴尔供应商处进货,后来换成了联想,那么“供应商”就是一个缓慢变化维度。
处理缓慢变化维度是Kimball数仓体系中永恒的话题,因为数据仓库的本质,以及维度表在维度建模中的基础作用,我们几乎总是要跟踪维度的变更(change tracking),以保留历史,并提供准确的查询和分析结果。在《The Data Warehouse Toolkit, 3rd Edition》一书的第5章,Kimball提出了多种缓慢变化维度的类型和处理方法,其中前五种是原生的,后面的方法都是混合方法(hybrid techniques),因此下面来看看前五种,即Type 0~Type 4。
SCD Type 0: Retain Original(维持原状)
一种特殊的SCD类型,即不管维度属性的实际值如何变化,数仓中维度的值都会维持第一次的值。它主要适用于那些本身含义就是“原始值”(original)的维度,比如在用户维度表中,用户注册时使用的原始用户名(original_user_name)。如果它发生变化,那么变化后的值是无效的,会被抛弃。
SCD Type 1: Overwrite(覆盖)
最简单的SCD类型,即一旦维度属性的实际值发生变化,就会直接覆写到数仓中。数仓中的维度属性总是且仅仅保存着最近一次变更的值(most recent assignment)。书中的例子如下:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-cd99050f0a27911f2be0a6125f7ccf04.png)
在上图中,Department Name维度发生了变化,并且新值直接覆盖了上一次的值。虽然它很容易实现,但是这样做会丢掉所有变更历史,并且在跨时域查询时,有可能会得到错误的结果。在实际操作中,这种方式几乎总是一种不良设计。
SCD Type 2: Add New Row(添加新行)
最主要、最常用的SCD类型,在我们日常以Hive为基础的数仓建设过程中,体现为拉链表技术。
这种类型在维度表中添加两个辅助列:该行的有效日期(effective date)和过期日期(expiration date),分别指示该行从哪个时间点开始生效,以及在哪个时间点过后会变为无效。每当一个或多个维度发生更改时,就创建一个新的行,新行包含有修改后的维度值,而旧行包含有修改前的维度值,且旧行的过期日期也会同步修改。书中的例子如下:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-0ca6eeb08823edfe5d721202e5bde239.png)
在上图中,当前有效列(current列)的过期日期会被记录为9999-12-31。当Department Name维度变化时,旧有的Product Key为12345的行的过期日期被更新为修改日期,并且新建了一个Key为25984的行,包含新的数据。
需要注意的是,这里的Product Key是所谓代理键(surrogate key),即不表示具体业务含义,而只是代表表内数据行的唯一ID。在处理SCD时,代理键可以直接用来区分同一自然键(natural key)的数据的新旧版本。上图中的SKU就是自然键。
这种类型的SCD处理方式能够非常有效且精确地保留历史与反映变更,但缺点是会造成数据的膨胀,因为即使只有一个维度变化,也要创建新行。
SCD Type 3: Add New Attribute(添加新属性列)
Type 2虽然非常好,但是当要在同一个时间维度内把新值和旧值关联起来时,就没有那么方便了。比如在上一节的表中,如果查询2013年2月1日以后的记录,就只能查到Department Name为“Strategy”的记录,而“Education”就被屏蔽了。Type 3就是一种与Type 2互补的类型。在Type 3的处理方法中,不会添加新行,而会添加一个新的属性列,该属性列中保存有对应维度的上一次变化的值。书中的例子如下:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-1b3edff13a2fb0b520d8b4a2499c9b2b.png)
在上图中新增了一个名称为“Prior Department Name”的列,保存着上一次变更的值。这样也解决了Type 2的数据膨胀问题,但是就只能保存一次变更历史,称为“变更现实”(alternate realities)。
另外仍然要注意,如果维度表中的许多维度都会发生类似的变更,那么就要新增很多列,这显然不太靠谱。所以这种类型经常用来处理那种变化可预测的(predictable)、“牵一发而动全身”的少数SCD。
当然,也可以根据实际需求新增多个列来保存多次变更历史:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-a551dd429c890e5c9cc2274bc483eba1.png)
SCD Type 4: Add Mini-Dimension(添加微维度)
当维度的变化没有那么“缓慢”时,前面三种类型的处理就都显得力不从心了(特别是对于规模非常大的维度表,比如有百万甚至千万行)。这种维度一般就不再称为SCD,而称为“快速变化维度”(rapidly changing dimensions, RCD)。当RCD的规模比较小时,还能够采用Type 2或者Type 3来撑着,但规模很大时,就只能采用Type 4了。Type 4的方式是将那些快速变化的维度从原来的大维度表中拆分出来单独处理,是为微维度(mini-dimension)。
以书中的内容为例,如果顾客维度中有一部分人口统计学(demographic)维度是RCD,就将它们拆成单独的维度表:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-2f59e5d8ad74294c0cd1aa484e1bce15.png)
其中,微维度表的维度最好是少量、分段的(banded)离散值,例如:
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-b4b1946a1d52d29f2b3be8ee10153d3e.png)
SCD处理方式的对比
下表仍然来自《数据仓库工具箱》的原文。注意其中除了Type 0~4之外,还有三种混合方式,即Type 5~7。
![数据仓库中的缓慢变化维度(SCD) 数据仓库中的缓慢变化维度(SCD)](https://www.top8488.top/wp-content/uploads/2020/04/frc-49eb7de1b3332ec634c20695b8d743ec.png)
最后善意提醒,《数据仓库工具箱(第三版)》这本书一定要读英文原版,千万不要读中译本。中译本错误百出,很多地方读起来都不通顺,令人窒息。
民那晚安~