图解数据仓库之拉链表,超简单!

 

1.背景分析

 在数据仓库的数据模型设计过程中,经常会遇到下面这种表的设计:

  1. 有一些表的数据量很大,比如一张用户表,大约10亿条记录,50个字段,这种表,即使使用ORC压缩,单张表的存储也会超过100G,在HDFS使用双备份或者三备份的话就更大一些。
  2. 表中的部分字段会被update更新操作,如用户联系方式,产品的描述信息,订单的状态等等。
  3. 需要查看某一个时间点或者时间段的历史快照信息,比如,查看某一个订单在历史某一个时间点的状态。
  4. 表中的记录变化的比例和频率不是很大,比如,总共有10亿的用户,每天新增和发生变化的有200万左右,变化的比例占的很小。

拉链表可以解决上述数据缓慢化场景问题,既能保存历史状态,又能尽量节省空间资源,数仓中某些维度缓慢变化的应用场景可以使用缓慢变化维拉链表。

2.图解拉链表形成过程

1)下图为业务后台数据库中每日的数据变化情况,每天只保留客户最新手机号码。

图解数据仓库之拉链表,超简单!_第1张图片

2)根据最后修改时间字段从业务后台数据库抽取最新被修改数据,形成分区表。

图解数据仓库之拉链表,超简单!_第2张图片

 

3)拉链表的形成过程(重点!!!)

图解数据仓库之拉链表,超简单!_第3张图片

 

3.过程分析

首先根据上面的分析过程,对sql比较熟悉的小伙伴估计已经能够想出整套的解决方案了。一般在工作中,和业务人员沟通之后,自己如果能清楚的描述出业务过程,基本上需求已经实现一大半了,接下来就是写代码啦!

1)整体思路分析

  1. 从业务后台数据库同步到数据仓库,形成初始化拉链表
  2. 每天根据数据修改时间字段抽取数据,形成日分区表
  3. 根据初始化拉链表(前一天)和日分区表(当日)形成当日拉链表

2)进一步思考

  1. 何时更新拉链表?答:业务数据发生变化时候,即当日分区表有数据就可以更新形成新的拉链表。
  2. 可以将上述三个步骤写成一个存储过程,后续直接调用即可生成拉链表,当然在大数据平台可已开发定时任务更新拉链表。
  3. 如何回滚拉链表,即回到前面的历史状态?答:这里主要应对更新拉链表出错的情况,后续再写另外的文章阐述。
  4. 如果t_end_time默认可以不是‘9999-12-31’这种永久有效状态,意味着用户的数据是有时效的,由此可以联想到用户购买会员的时效情景,这里拉链表更新时候需要注意一下会与普通t_end_time = '9999-12-31'的数据会有所不同。

4.代码实现

1)算法思想

1、采集当日全量数据到ND(NewDay)表;					
					
2、可从历史表中取出昨日全量数据存储到OD(OldDay)表;					
					
3、(ND-OD)就是当日新增和变化的数据,也就是当天的增量,用W_I表示;					
					
4、(OD-ND)为状态到此结束需要封链的数据,用W_U表示;					
					
5、将W_I表的内容全部插入到历史表中,这些是新增记录,start_date为当天,而end_date为max值;					
					
6、对历史表进行W_U部份的更新操作,start_date保持不变,而end_date改为当天,也就是关链操作					

 2)代码实现

    1. ods层user表结构

CREATE EXTERNAL TABLE ods.user (
  user_num STRING COMMENT '用户编号',
  mobile STRING COMMENT '手机号码',
  reg_date STRING COMMENT '注册日期',
  modified_time STRING COMMENT '更新日期'
COMMENT '用户资料表'
PARTITIONED BY (dt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user';
)

    2. ods层user_update表结构

CREATE EXTERNAL TABLE ods.user_update (
  user_num STRING COMMENT '用户编号',
  mobile STRING COMMENT '手机号码',
  reg_date STRING COMMENT '注册日期',
  modified_time STRING COMMENT '更新日期'
COMMENT '每日用户资料更新表'
PARTITIONED BY (dt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user_update';
)

    3. 拉链表结构

CREATE EXTERNAL TABLE dws.user_his (
  user_num STRING COMMENT '用户编号',
  mobile STRING COMMENT '手机号码',
  reg_date STRING COMMENT '注册日期',
  modified_time STRING COMMENT '更新日期',
  t_start_date STRING COMMENT '数据开始时间',
  t_end_date STRING COMMENT '数据结束时间'
COMMENT '用户资料拉链表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/dws/user_his';
)

    4. 拉链表从2017-01-01更新到2017-01-02

INSERT OVERWRITE TABLE dws.user_his
SELECT * FROM
(
    SELECT A.user_num,
           A.mobile,
           A.reg_date,
           A.modified_time
           A.t_start_time,
           CASE
                WHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-01'
                ELSE A.t_end_time
           END AS t_end_time
    FROM dws.user_his AS A
    LEFT JOIN ods.user_update AS B
    ON A.user_num = B.user_num
UNION
    SELECT C.user_num,
           C.mobile,
           C.reg_date,
           C.modified_time,
           '2017-01-02' AS t_start_time,
           '9999-12-31' AS t_end_time
    FROM ods.user_update AS C
) AS T

     5. 拉链表从2017-01-02更新到2017-01-03

INSERT OVERWRITE TABLE dws.user_his
SELECT * FROM
(
    SELECT A.user_num,
           A.mobile,
           A.reg_date,
           A.modified_time
           A.t_start_time,
           CASE
                WHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-02'
                ELSE A.t_end_time
           END AS t_end_time
    FROM dws.user_his AS A
    LEFT JOIN ods.user_update AS B
    ON A.user_num = B.user_num
UNION
    SELECT C.user_num,
           C.mobile,
           C.reg_date,
           C.modified_time,
           '2017-01-03' AS t_start_time,
           '9999-12-31' AS t_end_time
    FROM ods.user_update AS C
) AS T

     6. 替换日期为变量,比如今日是2017-01-03,数仓中可以更新2017-01-02数据,此时$date  = 2017-01-02

INSERT OVERWRITE TABLE dws.user_his
SELECT * FROM
(
    SELECT A.user_num,
           A.mobile,
           A.reg_date,
           A.modified_time
           A.t_start_time,
           CASE
                WHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN date_add($date,-1)
                ELSE A.t_end_time
           END AS t_end_time
    FROM dws.user_his AS A
    LEFT JOIN ods.user_update AS B
    ON A.user_num = B.user_num
UNION
    SELECT C.user_num,
           C.mobile,
           C.reg_date,
           C.modified_time,
           $date AS t_start_time,
           '9999-12-31' AS t_end_time
    FROM ods.user_update AS C
) AS T

4.评价:拉链表对比全量表和分区表

    1. 下图展示拉链表,分区表和全量表的结果

图解数据仓库之拉链表,超简单!_第4张图片

    2.三种类型的表各自的对比如下,当然每种表有每种表各自适合的使用场景,具体问题要具体分析!

图解数据仓库之拉链表,超简单!_第5张图片

写在最后

    文中若有不正之处,望请指正!

你可能感兴趣的:(04--数据开发,数据仓库,大数据)