聊聊数据仓库中的缓慢变化维度(SCD)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊数据仓库中的缓慢变化维度(SCD)相关的知识,希望对你有一定的参考价值。

参考技术A 虽然我的主业是实时计算和批量计算,并不是数仓,但是在日常工作中绝对少不了与数仓打交道。并且我也算是参与过离线数仓建设的,维度建模的基础还是不能忘。本文就作为一篇抄书笔记吧。

顾名思义,缓慢变化维度(slowly changing dimension, SCD)就是数据仓库维度表中,那些随时间变化比较不明显,但仍然会发生变化的维度。考虑以下两个情境:

处理缓慢变化维度是Kimball数仓体系中永恒的话题,因为数据仓库的本质,以及维度表在维度建模中的基础作用,我们几乎总是要跟踪维度的变更(change tracking),以保留历史,并提供准确的查询和分析结果。在《The Data Warehouse Toolkit, 3rd Edition》一书的第5章,Kimball提出了多种缓慢变化维度的类型和处理方法,其中前五种是原生的,后面的方法都是混合方法(hybrid techniques),因此下面来看看前五种,即Type 0~Type 4。

一种特殊的SCD类型,即不管维度属性的实际值如何变化,数仓中维度的值都会维持第一次的值。它主要适用于那些本身含义就是“原始值”(original)的维度,比如在用户维度表中,用户注册时使用的原始用户名(original_user_name)。如果它发生变化,那么变化后的值是无效的,会被抛弃。

最简单的SCD类型,即一旦维度属性的实际值发生变化,就会直接覆写到数仓中。数仓中的维度属性总是且仅仅保存着最近一次变更的值(most recent assignment)。书中的例子如下:

在上图中,Department Name维度发生了变化,并且新值直接覆盖了上一次的值。虽然它很容易实现,但是这样做会丢掉所有变更历史,并且在跨时域查询时,有可能会得到错误的结果。在实际操作中,这种方式几乎总是一种不良设计。

最主要、最常用的SCD类型,在我们日常以Hive为基础的数仓建设过程中,体现为拉链表技术。

这种类型在维度表中添加两个辅助列:该行的有效日期(effective date)和过期日期(expiration date),分别指示该行从哪个时间点开始生效,以及在哪个时间点过后会变为无效。每当一个或多个维度发生更改时,就创建一个新的行,新行包含有修改后的维度值,而旧行包含有修改前的维度值,且旧行的过期日期也会同步修改。书中的例子如下:

在上图中,当前有效列(current列)的过期日期会被记录为9999-12-31。当Department Name维度变化时,旧有的Product Key为12345的行的过期日期被更新为修改日期,并且新建了一个Key为25984的行,包含新的数据。

需要注意的是,这里的Product Key是所谓代理键(surrogate key),即不表示具体业务含义,而只是代表表内数据行的唯一ID。在处理SCD时,代理键可以直接用来区分同一自然键(natural key)的数据的新旧版本。上图中的SKU就是自然键。

这种类型的SCD处理方式能够非常有效且精确地保留历史与反映变更,但缺点是会造成数据的膨胀,因为即使只有一个维度变化,也要创建新行。

Type 2虽然非常好,但是当要在同一个时间维度内把新值和旧值关联起来时,就没有那么方便了。比如在上一节的表中,如果查询2013年2月1日以后的记录,就只能查到Department Name为“Strategy”的记录,而“Education”就被屏蔽了。Type 3就是一种与Type 2互补的类型。在Type 3的处理方法中,不会添加新行,而会添加一个新的属性列,该属性列中保存有对应维度的上一次变化的值。书中的例子如下:

在上图中新增了一个名称为“Prior Department Name”的列,保存着上一次变更的值。这样也解决了Type 2的数据膨胀问题,但是就只能保存一次变更历史,称为“变更现实”(alternate realities)。

另外仍然要注意,如果维度表中的许多维度都会发生类似的变更,那么就要新增很多列,这显然不太靠谱。所以这种类型经常用来处理那种变化可预测的(predictable)、“牵一发而动全身”的少数SCD。

当然,也可以根据实际需求新增多个列来保存多次变更历史:

当维度的变化没有那么“缓慢”时,前面三种类型的处理就都显得力不从心了(特别是对于规模非常大的维度表,比如有百万甚至千万行)。这种维度一般就不再称为SCD,而称为“快速变化维度”(rapidly changing dimensions, RCD)。当RCD的规模比较小时,还能够采用Type 2或者Type 3来撑着,但规模很大时,就只能采用Type 4了。Type 4的方式是将那些快速变化的维度从原来的大维度表中拆分出来单独处理,是为微维度(mini-dimension)。

以书中的内容为例,如果顾客维度中有一部分人口统计学(demographic)维度是RCD,就将它们拆成单独的维度表:

其中,微维度表的维度最好是少量、分段的(banded)离散值,例如:

下表仍然来自《数据仓库工具箱》的原文。注意其中除了Type 0~4之外,还有三种混合方式,即Type 5~7。

最后善意提醒,《数据仓库工具箱(第三版)》这本书一定要读英文原版,千万不要读中译本。中译本错误百出,很多地方读起来都不通顺,令人窒息。

民那晚安~

SCD缓慢变化维拉链表

                           SCD缓慢变化维拉链表SQL实现

1 缓慢变化维概述

SCD英文Slow Changing Dimensions(缓慢变化维),它是数据仓库建模过程中一个非常重要的概念。众所周知数据仓库是基于历史数据的,而历史数据的变化依赖于维度的定义,缓慢变化维就是用来跟踪和表现维度表变化的一种方法。事实表往往在跟维度表关联时需要“卡”住相应的时间节点,这就是SCD记录历史变化的作用。

注: 1 当前代码演示环境是SQL Server,基于Merge语法,其它数据库类似。

      2 SCD缓慢变换维Kettle实现

      3 Kettle转换作业应用50个案例脚本 

数据仓库里维度表的字段改变(变化)常见的有3种,分别记作Type1、Type2、Type3。这里假如我们有用户维度表customer客户(cust_id用户编号、name姓名、jobtitle职位),如果在某个时间将某个用户的职位由Dev更新成CTO,我们看下三种维度处理的方法的情况:

  • Type1(不会记录维度里关键字段值的变化历史情况):
cust_idnamejobtitle
1张三Dev

新的customer表为:

cust_idnamejobtitle
1张三CTO
  • Type2(记录维度里关键字段每次的变化情况):

针对1的情况假设更新时间是2020-10-21,那么对应到客户维度表里(这里end_date对应最新时一般设置为9999-12-31或者NULL),则有如下的轨迹:

cust_idnamejobtitlestart_dateend_dateis_current
1张三Dev2020-10-102020-10-210
1张三CTO2020-10-21NULL1
  • Type3(直接记录当前最新的值和上一次变化前的值)
cust_idnamejobtitlepre_jobtitle
1张三CTODev

综上所述不难发现type1和type3不能很好的记录维度的每次变化情况,type1没有,type3只能记录最新的一次变化,而type2会记录每次变化(关注的字段需自定义)的情况。

注:一般来说事实表会存放维度表的ID,而事实表抽取(ETL)的过程中是通过维度表的start_date和end_date来确定事实表里度量(指标)的时间范围(即维度表的对应时间)。

2 代码与注释

2.1 表结构与数据

-- step1 准备表和数据,当前运行在SQL Server里。

-- 业务系统(OLTP)的客户表
CREATE TABLE Customer(
       ID int IDENTITY(1,1) NOT NULL,
       FullName nvarchar(50) NULL,
       City nvarchar(50) NULL,
       Occupation nvarchar(50) NULL)

      
-- 数据仓库的(OLAP)的客户维度表
CREATE TABLE DimCustomer(
              CustomerID int IDENTITY(1,1) NOT NULL,
              CustomerAlternateKey int NULL,
              FullName nvarchar(50) NULL,
              City nvarchar(50) NULL,
              Occupation nvarchar(50) NULL,
              StartDate datetime NULL,
              EndDate datetime NULL,
              IsCurrent bit NULL,
       PRIMARY KEY CLUSTERED
       (
              CustomerID ASC
       )
)

GO

ALTER TABLE DimCustomer ADD  DEFAULT ((1)) FOR IsCurrent
INSERT INTO Customer(FullName,City,Occupation)
SELECT 'BIWORK','Beijing','CEO' UNION ALL
SELECT 'ZhangSan','Shanghai','Education' UNION ALL
SELECT 'Lisi','Guangzhou','IT' UNION ALL
SELECT 'Wangwu','Beijing','Finance'

2.2 缓慢变换维代码

-- step2 SCD 模块
-- 1 修改状态
MERGE INTO dbo.DimCustomer AS Dim
USING dbo.Customer AS Src
    ON Dim.CustomerAlternateKey = Src.ID
WHEN NOT MATCHED BY TARGET
    THEN INSERT VALUES(Src.ID,Src.FullName,Src.City,Src.Occupation,GETDATE(),NULL,1)
WHEN MATCHED AND (Dim.City <> Src.City OR Dim.Occupation <>  Src.Occupation) AND Dim.IsCurrent=1

    THEN UPDATE SET Dim.EndDate =CASE WHEN Dim.EndDate IS NULL THEN GETDATE() ELSE Dim.EndDate END,Dim.IsCurrent = 0;

-- 2 修改数据
MERGE INTO dbo.DimCustomer AS Dim
USING dbo.Customer AS Src
    ON Dim.CustomerAlternateKey = Src.ID
       AND Dim.City = Src.City AND Dim.Occupation = Src.Occupation
       WHEN NOT MATCHED BY TARGET
       THEN INSERT VALUES(Src.ID,Src.FullName,Src.City,Src.Occupation,getDATE(),NULL,1);

2.3  修改数据验证

-- Step3 验证
-- 新插入一条
INSERT INTO Customer(FullName,City,Occupation) VALUES
('qinliu','Beijing','Finance')


-- Case1: 执行如下更新后执行SCD模块,这里的ID依赖于自增序列生成的序号

UPDATE Customer
SET Occupation = 'IT'
WHERE ID = 6


-- Case2: 执行如下更新后执行SCD模块,这里的ID依赖于自增序列生成的序号
UPDATE Customer
SET Occupation = 'Publisher',
    City = 'Hangzhou'
WHERE ID = 6

-- 每次修改后对照查看DimCustomer表的变化,查看是否追踪到数据的历史变更信息。以下为查询示例:
SELECT * 
FROm DimCustomer
WHERE FullName='qinliu'

以上是关于聊聊数据仓库中的缓慢变化维度(SCD)的主要内容,如果未能解决你的问题,请参考以下文章

如何最好地处理缓慢变化维度 (SCD2) 中的历史数据变化

渐变维度 (SCD) 类型 2 的不同场景

数据仓库系列 - 缓慢渐变维度

kettle学习之--缓慢变化维度(SCD)

hive缓慢变化维

事实表与维度表