数据仓库中的缓慢变化维

什么是缓慢变化维?

要解释缓慢变化维,必须先解释什么是维度。

什么是维度?

在数据仓库的 DW 层中,表根据用途往往会分为 2 个类型:FACT(事实表)和 DIM(维度表)。

举个例子,如果我们要描述一个餐饮过程:

小明 2020年4月19日下午3点20分 在 海底捞(万达广场) 吃了5道菜,每道菜的单价是4元,总价是20元。

那么这个过程在数仓中,会如此划分:

fact:餐饮过程,单价、数量、总价

dim:小明,餐饮时间,餐饮门店,菜名。

也就是说:吃了多少东西,多少钱——这些属于 fact;在哪里吃、什么时候吃?这些属于 dim。

下面是简单的 ER 图,方便大家更好的理解。

黄色为事实表,蓝色的就是维度表。

数据仓库中的缓慢变化维

什么是缓慢变化维?

正如上述所言,我们会将分析的各种角度,存放在维度表中。但正如每个人所见,维度里的数据是可能发生变化的——尽管可能跨越极久。

举2个例子:

客户的性别变更

可能在第一次登陆中,我们得到的信息是 该客户性别为男。

但在几年的客户再一次使用中,我们又得到该客户的性别为女。

这就是维度值的一种变化可能

性别一般并不会改变,所以大概率是其中的一次数据有误。但也有可能是客户做了变性手术。

雇员的部门更替

假定有一个雇员叫小杨,他最早是负责运营的——此时他的 title 是"商品运营助理";但因为某些原因,他转组成为数据组的一员,这时 title 就变成了"数据分析专员"。

这是缓慢变化维的一种常见可能

上面提到的这些数据变化,业务系统(CRM、OA等)往往并不会保留历史数据。但在分析角度,我们是一定要保留这些改变的痕迹。这种随着时间可能会缓慢变化的维度,就是 缓慢变化维、也就是 SCD(Slowly Changing Dimensions)

常见的处理方法

kimball 整理的处理方法一共有 8 种,但往往只有 3 种被详细使用。

类型1 重写

与业务数据保持一致,直接 update 为最新的数据。

这种方法主要应用于以下两种情况:

数据必须正确——例如用户的身份证号,如需要更新则说明之前录入错误。

无需考虑历史变化的维度——例如用户的头像 url,这种数据往往并没有分析的价值。因此不做保留。

数据仓库中的缓慢变化维

这种处理方式的优缺点:

优点:

简化 ETL ——直接 update 即可。

节省存储空间——其他存储方法都占用更多空间。

缺点:

无法保留历史痕迹——万一有天想分析呢?

类型2 增加新行

更新历史数据时间戳,新增新行记录新值。

这种方法主要用于 仅需要保存历史数据 的业务场景

具体的 ETL 则如下:

自然键即指有业务意义的唯一 ID,例如用户 ID、身份证号等。代理键则可以简单理解为该表的自增 ID 值

自然键第一次出现时。

新增一行数据,created 为业务系统的创建时间,updated 为9999-12-31

数仓的规范不允许数据存在 NULL 值的情况,因此用9999-12-31代替

类型2的维度发生变化时

将自然键当前记录的 updated 由 9999-12-31刷为最新时间

新增一行记录,记录最新的数据,created 为最新时间,updated 为 9999-12-31

数据仓库中的缓慢变化维

这样一来,因为事实表存储的是维度表的代理键而非自然键,因此在历史数据的查询中会以历史的维度值进行计算。同时在维度值更新后的相关数据自然使用的是新的代理键。完美的解决了大部分缓慢变化维情况。

类型3 增加当前值属性

在大部分的维度模式中,很多的源数据变化将产生类型 1 和类型 2 变化。有时两种技术都不能满足需求——当需要分析所有 伴随着新值或旧值的变化前后 记录的事实时,需要采用类型 3 变化。

很多人都难以理解类型 3 的重要性,因此笔者举一个例子——一个无法用类型 1 和类型 2 处理的例子:
假定一家公司的销售是按照销售区域进行分组:
数据仓库中的缓慢变化维
突然有一天,领导灵机一动,决定 精细化销售,将东部、南部、北部重新划分为东南、东北部

数据仓库中的缓慢变化维
但由于发送的过于仓促,因此销售人员是立刻使用了新的部门划分;但同样希望保留旧的名称——至少要暂时保留,用以比较今年和去年的业绩。即:

拥有使用 新区域 分析所有事实的能力,无论变化前还是变化后

拥有使用 旧区域 分析所有事实的能力,无论变化前还是变化后

第一个需求——新区域分析——允许立即采用新的分组,所有历史订单都能分为东南、东北等新类别;

第二个需求——旧区域分析——允许公司采用旧分组,所有的订单可以根据旧值分组——就好像一切都没发生过变化。

这时,就会发生一些问题:先前的技术不适合——无论是类型1还是类型2,都不能同时满足这两个需求;

类型 1 可以满足第一种需求,使用新值写旧值。但显然它无法实现第二个需求;

类型 2 则更糟,它不能满足任意一个需求——旧的事实和旧的维度相连;而新的维度值和以后的事实相连。毫无疑问,它既不能分析旧数据、也不能分析新数据。

此时引入类型 3 处理方法:新增字段同时储存新旧值。

如果发生第二次变化,当前的 current 会被更新到 previous 中,新的变化值则会写入 current。

需要注意的是:类型 3 不保存事实的历史内容

需要注意的是,类型 3 的改变往往并不是一个仅此一次的过程——它能发生 1 次就有可能发生 2 次甚至更多次。类型 3 变化只保护变化属性的一个旧版本,一旦发生第二次变化,第一次变化前的值就要被废弃了。如果想要用变化 3 来实现更多的版本,那只能增加更多的列来实现(例如 dpt2018,dpt2019)——这无疑是非常愚蠢的。因此,除非特定需要,应尽量避免使用类型 3 的变化。

相关文章: