吴乙己的数仓指南_5.1维度建模技巧之SCD2类型缓慢变化维搭建

0.引言

缓慢变化维技术(Slow Changing Dimension,简称SCD)是维度建模中几乎无时无刻不在使用的技术,它为维度提供了基于历史数据进行切片、切块的能力。

1.SCD类型

以kimball老爷子的理论,共有7种SCD类型,但在日常的数仓开发中,最常使用的是SCD2。所谓SCD2类型,即在维度表中增加新行记录最新维度

2.举个栗子

假设我们有一张地域维度表,记录了行政区号、地名信息。建表语句如下:

--基于HQL方言
CREATE TABLE DIM_ADD(
ADD_NO STRING COMMENT '地域编码',
ADD_NAME STRING COMMENT '地域名称'
)

其中有一行数据:

ADD_NOADD_NAME
010北京

我们知道,在100年前,北京还不叫“北京”,叫做“北平”,如果我们要基于100年前的数据进行分析,那彼时的地名字段应写做“北平”。可是我们的维表中没有“北平”,该怎么办呢?

3.SCD2

3.1 什么是SCD2

为了解决第二节的问题,SCD2就是一种非常优雅的办法。

SCD2,Kimball老爷子只总结了两个字——“新增”。

说白了,就是在维度表中保留历史数据新增最新数据,并以数据的生效时间段来约束数据的有效性。

一张简单的SCD2类型维度表建表语句如下:

--基于HQL方言
CREATE TABLE DIM_ADD_SCD2(
ADD_NO STRING COMMENT '地域编码',
ADD_NAME STRING COMMENT '地域名称',
FROM_DATE STRING COMMENT '起始时间',
TO_DATE STRING COMMENT '结束时间'
)

接着,北平的数据就可以维护进来

ADD_NOADD_NAMEFROM_DATETO_DATE
010北平1368-09-121947-09-27
010北京1947-09-289999-12-31

我们可以看到,在SCD2类型中,维护了FROM_DATETO_DATE,代表了起始时间结束时间,业务时间落在其中时就叫生效时间段

注意
1.自然键相同的维度值,各行之起止时间跨度,不得有交集,从业务上讲不合逻辑,同时会导致数据翻倍。
2.最新的数据,结束时间写“9999-12-31”即可。
3.若基于HQL开发,起始时间、结束时间可直接使用STRING类型,与STRING、DATE类型变量都可以直接进行比较。

3.2 如何使用SCD2

在使用SCD2类型的维度表时也非常简单,只需在WHERE条件或JOIN条件中使用业务时间去限制起始时间、结束时间即可。

如现在有一分析需求,欲从户口事实表中计算1942年各个城市登记入籍的人数。

户口事实表建表语句如下:

--基于HQL方言
CREATE TABLE FACT_HOUSEHOLD(
HOUSEHOLD_NO STRING COMMENT '户口编码',
ADD_NO STRING COMMENT '地域编码',
HOUSEHOLD_CNT INT COMMENT '在籍人数',
HOUSEHOLD_RECORD_DATE STRING COMMENT '入籍时间'
)

数据示例:

HOUSEHOLD_NOADD_NOHOUSEHOLD_CNTHOUSEHOLD_RECORD_DAY
000000000101021937-09-27
000000000302932013-09-01
0000000004053332010-09-01

那么需求指标计算SQL为:

--基于HQL方言
SELECT T2.ADD_NAME AS ADD_NAME
      ,SUM(NVL(T1.HOUSEHOLD_CNT,0)) AS ADD_POPULATION
  FROM FACT_HOUSEHOLD T1
  JOIN DIM_ADD_SCD2 T2
     ON T1.ADD_NO = T2.ADD_NO
    AND SUBSTR(T2.TO_DATE,1,4) >= '1942'
    AND SUBSTR(T2.TO_DATE,1,4) <= '1942'
  WHERE SUBSTR(HOUSEHOLD_RECORD_DAY,1,4) = '1942'
  ;
--一般而言,传参会写占位符基于业务时间生成,此为示例,故写为固定值。

以上便是一个简易的demo,介绍了如何在计算指标时使用SCD2类型维度表。

3.3 如何实现SCD2

仍以3.2节中的DIM_ADD_SCD2为例,要对其进行更新,更新的依据是通过ETL获取的最新地域维度信息表——DIM_ADD_NEW。

其建表语句如下:

--基于HQL方言
CREATE TABLE DIM_ADD_NEW(
ADD_NO STRING COMMENT '地域编码',
ADD_NAME STRING COMMENT '地域名称'
)

设其中数据如下:

ADD_NOADD_NAME
010北京
002A3
003B1
004C1

我们对DIM_ADD_NEW中的数据分而治之:

情况一,在自然键对应时,DIM_ADD_NEW中与DIM_ADD_SCD2最新一条数据无差异,则不做操作。

情况二,在自然键对应时,DIM_ADD_NEW中与DIM_ADD_SCD2最新一条数据有差异,则将此自然键DIM_ADD_SCD2中最新一条数据结束时间设置为业务时间(或业务时间-1d,视具体业务而定),并插入最新维度数据,起始时间设置为业务时间+1d(或业务时间,视具体业务而定),结束时间设置为’9999-12-31’。

情况三,之于某自然键,DIM_ADD_NEW中存在而DIM_ADD_SCD2中不存在,新增之,起始时间设置为业务时间+1d(或业务时间,视具体业务而定),结束时间设置为’9999-12-31’。

下面我们以此命题进行一个小demo代码的撰写:

3.3.1 获取维度基准

设维表有数据如下:

ADD_NOADD_NAMEFROM_DATETO_DATE
010北平1368-09-121947-09-27
010北京1947-09-289999-12-31
002A11234-01-011949-10-01
002A21949-10-029999-12-31
003B11234-01-019999-12-31

获取DIM_ADD_SCD2结束日期为9999-12-31的数据作为基准。

--基于HQL方言
CREATE TABLE DIM_ADD_SCD2_NEWEST AS
SELECT T1.ADD_NO
      ,T1.ADD_NAME
  FROM DIM_ADD_SCD2 T1
 WHERE T1.TO_DATE = '9999-12-31'
 ;

处理过后数据如下:

ADD_NOADD_NAME
010北京
002A2
003B1
3.3.2 按情况分治打标

对 3.3 中提到的三种情况,对最新数据进行打标,打标字段命名为FLAG,0对应情况一,1对应情况2,2对应情况三。

建表语句如下:

--基于HQL方言
CREATE TABLE DIM_ADD_NEW_FLAG(
ADD_NO STRING COMMENT '地域编码',
ADD_NAME STRING COMMENT '地域名称',
FLAG STRING COMMENT '情况标识'
)

对应情况一,有代码:

--基于HQL方言
INSERT INTO TABLE DIM_ADD_NEW_FLAG
SELECT T1.ADD_NO AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,'1' AS FLAG
  FROM DIM_ADD_NEW T1
  JOIN DIM_ADD_SCD2_NEWEST T2 --JOIN起过滤作用
    ON T1.ADD_NO = T2.ADD_NO 
   AND T1.ADD_NAME = T2.ADD_NAME 
   ;

对应情况二,有代码:

--基于HQL方言
INSERT INTO TABLE DIM_ADD_NEW_FLAG
SELECT T1.ADD_NO AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,'2' AS FLAG
  FROM DIM_ADD_NEWT1
  LEFT JOIN DIM_ADD_SCD2_NEWEST T2
    ON T1.ADD_NO = T2.ADD_NO 
   AND T1.ADD_NAME = T2.ADD_NAME 
 WHERE T2.ADD_NO IS NULL --关联不上,代表有更新数据
   AND T1.ADD_NO  IN (SELECT ADD_NO  FROM OLD_NEWEST) --自然键需重合
   ;

对应情况三,有代码:

--基于HQL方言
INSERT INTO TABLE DIM_ADD_NEW_FLAG
SELECT T1.ADD_NO AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,'3' AS FLAG
  FROM DIM_ADD_NEW T1
  LEFT JOIN DIM_ADD_SCD2_NEWEST T2
    ON T1.ADD_NO = T2.ADD_NO 
   AND T1.ADD_NAME = T2.ADD_NAME 
 WHERE T2.ADD_NO IS NULL --关联不上,代表是新数据
   AND T1.ADD_NO  NOT IN (SELECT ADD_NO  FROM OLD_NEWEST) --自然键需不对称
   ;

则此时,DIM_ADD_NEW_FLAG有数据:

ADD_NOADD_NAMEFLAG
010北京1
002A32
003B11
004C13
3.3.3更新维度表

本demo基于HQL方言,故无法进行UPDATE操作,在更新时需使用如下代码:

首先,生成一个临时表,作为处理结果的载体,并以此更新维度表,建表语句与DIM_ADD_SCD2相同:

--基于HQL方言
CREATE TABLE DIM_ADD_SCD2_TMP(
ADD_NO STRING COMMENT '地域编码',
ADD_NAME STRING COMMENT '地域名称',
FROM_DATE STRING COMMENT '起始时间',
TO_DATE STRING COMMENT '结束时间'
)

对于情况1,使用自然键将DIM_ADD_SCD2 与DIM_ADD_NEW_FLAG关联,原样装载至临时表即可 :

--基于HQL方言
INSERT INTO TABLE DIM_ADD_SCD2_TMP
SELECT T1.ADD_NO AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,T1.FROM_DATE AS FROM_DATE 
      ,T1.TO_DATE AS TO_DATE 
  FROM DIM_ADD_SCD2 T1
  JOIN DIM_ADD_NEW_FLAG T2
    ON T1.ADD_NO  = T2.ADD_NO 
   AND T2.FLAG = '1'
    ;

对于情况二,我们需要将原维度表非最新数据原样装载。最新一条数据的结束时间设为业务时间。并基于DIM_ADD_NEW_FLAG新增一条数据,开始时间为业务时间+1d,结束时间为‘9999-12-31’。

--基于HQL方言
--原样装载非最新数据
INSERT INTO TABLE DIM_ADD_SCD2_TMP
SELECT T1.ADD_NO AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,T1.FROM_DATE AS FROM_DATE 
      ,T1.TO_DATE AS TO_DATE 
  FROM DIM_ADD_SCD2 T1
  JOIN DIM_ADD_NEW_FLAG T2
    ON T1.ADD_NO  = T2.ADD_NO 
   AND T2.FLAG = '2'
WHERE T1.TO_DATE != '9999-12-31'

UNION ALL

--将最新一条数据至结束时间设置为业务时间
SELECT T1.ADD_NO  AS ADD_NO 
      ,T1.ADD_NAME AS ADD_NAME 
      ,T1.FROM_DATE AS FROM_DATE 
      ,'${business_tm}'  AS TO_DATE --一般而言会传入一个业务时间,此处用占位符表示
  FROM DIM_ADD_SCD2 T1
  JOIN DIM_ADD_NEW_FLAG T2
    ON T1.ADD_NO  = T2.ADD_NO 
   AND T2.FLAG = '2'
WHERE T1.TO_DATE = '9999-12-31' 

UNION ALL

--插入最新维度值
SELECT ADD_NO  AS ADD_NO 
      ,ADD_NAME AS ADD_NAME 
      ,DATE_ADD('${business_tm}',1) AS FROM_DATE 
      ,'9999-12-31' AS TO_DATE
  FROM DIM_ADD_NEW_FLAG
WHERE FLAG = '2' 
    ;

对于情况三,将新数据装载至维度表即可,有代码如下:

--基于HQL方言
INSERT INTO TABLE DIM_ADD_SCD2_TMP
SELECT ADD_NO  AS ADD_NO 
      ,ADD_NAME AS ADD_NAME 
      ,DATE_ADD('${business_tm}',1) AS FROM_DATE 
      ,'9999-12-31' AS TO_DATE
  FROM DIM_ADD_NEW_FLAG
WHERE FLAG = '3' 

最后,我们将临时表的数据覆写至维度表。

--基于HQL方言
INSERT OVERWRITE TABLE DIM_ADD_SCD2
SELECT ADD_NO  AS ADD_NO 
      ,ADD_NAME AS ADD_NAME 
      ,'${business_tm}' AS FROM_DATE 
      ,‘9999-12-31’ AS TO_DATE
  FROM DIM_ADD_SCD2_TMP

新维表中数据如下:

ADD_NOADD_NAMEFROM_DATETO_DATE
010北平1368-09-121947-09-27
010北京1947-09-289999-12-31
002A11234-01-011949-10-01
002A21949-10-022021-11-20
002A32021-11-219999-12-31
003B11234-01-019999-12-31
004C12021-11-209999-12-31

至此,一轮SCD2类型的维表更新完毕。


以上就是本章内容,相信大家已经对SCD2类型是什么,如何使用,如何搭建有了清晰的认识。

下一章讲解蜈蚣事实表的归约优化,敬请期待。

【版权所有,翻版必究。】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值