1 缓慢变化维(SCD)

处理维度表的历史变化信息的问题称为处理缓慢变化维的问题,简称SCD(Slowly Changing Dimensions)问题
处理缓慢变化维的方法有以下几种常见方式:

  • 保留原值
  • 直接覆盖
  • 增加新属性列
  • 快照表
  • 拉链表

在从 OLTP 业务数据库向 DW 数据仓库抽取数据的过程中,特别是第一次导入之后的每一次增量抽取往往会遇到这样的问题:
业务数据库中的一些数据发生了更改,到底要不要将这些变化也反映到数据仓库中?在数据仓库中,哪些数据应该随之变化,哪些可以不用变化?
考虑到这些变化,在数据仓库中的维度表又应该如何设计以满足这些需要。

维度能够根据变化剧烈程度主要分为无变化维度、缓慢变化维度和剧烈变化维度。
例如一我的的相关信息,身份证号、姓名和性别等信息数据属于不变的部分,政治面貌和婚姻状态属于缓慢变化部分,
而教育经历、工作单位和竞赛经历等在某种程度上属于急剧变化字段.

但是在数据仓库中,其数据主要的特征一是静态历史数据,二是少改变不删除,三是定期增长,其作用主要用来数据分析。

因此分析的过程中对历史数据就提出了要求,有一些数据是需要能够反映出在周期内的变化历史,有一些数据缺不需要,那么这些数据应该如何来控制。

2 种类

数据仓库里维度的变化较慢,常用的有三类,分别为:

2.1 缓慢渐变类型1

不记录历史数据,直接覆写到数仓中: 如用户A年龄由 21 变成 22, 始终是1条记录(用户A年龄记录发生更改)

2.2 缓慢渐变类型2

每一次出现变更,就要添加一行新的记录;
尽可能的维护来自业务系统中的历史数据,捕获历史数据的变化。用户A年龄由 21 变成 22, 变成2条(用户A)记录;
因为多次变更而演变为了多条数据,原始业务表中的ID将不再具有唯一性,此时需要引入代理主键(Surrogate Key,唯一标识数据仓库表记录的键)。其次,维度表中的每一条数据都需要添加时间标签标识生效日期和位置。

2.3 缓慢渐变类型3(添加历史列)

3型SCD是2型SCD的一种改良处理,把新增行改为了新增属性;
只维护更少的历史记录,只记录上一次的历史信息;
直接记录当前最新的值和上一次变化前的值,加一列记录变化前的值;
3型SCD常表现为”曾用名”、”曾用地址”、”之前的浏览商品” 之类的属性列。

3 实现

3.1 类型1

覆盖写入即可

  1. insert overwrite table user -- 可以考虑置换为分区表
  2. select * from user_new;

3.2 类型2: 拉链表技术

处理思路主要分两步,首先是设置代理主键,然后基于代理主键添加一条新的数据,最后把原数据中的时间标识进行处理(也可不处理):

  1. -- 代理主键的设置(假设原来的user表中未设置代理主键)
  2. create table user_test -- 创建一个带代理主键的表user_test
  3. select dense_rank() over (partition by user_id) as agent_key, *
  4. from user;
  1. -- 基于代理主键添加一条新的数据(假设原表自带时间字段)
  2. insert into user_test
  3. select dense_rank() over (partition by user_id) as agent_key, *
  4. from user_test
  5. where user_id = 10001 and time = sysdate('yyyyMMdd',-1);
  1. -- 修改原来表中此user_id的非最新数据所对应的日期为'9999-01-01'(也可不改)
  2. update user_test
  3. set time = '9999-01-01'
  4. where user_id = 10001 and time != sysdate('yyyyMMdd',-1);

查找最新的数据:
可以通过代理键找最大的(主键往往是自增的,最大的通常是最新的数据)
查找历史时间的数据:

3.3 实现类型3

  1. -- 创建一个表用于添加新的属性列,也可在原表上使用alter处理
  2. create table user_test
  3. select *, address as last_address -- 添加一个last_address
  4. from user;
  1. -- 将用户新的address更新到表中
  2. update table user
  3. set user.address = user_test.address
  4. from user join user_test on user.user_id = user_test.user_id
  5. where user.user_id = 10001;