Note:
需要三张表:初始全量表、拉链表、增量表(存储发生变化数据的表)
第一步:创建一个 跟全量表一样表结构的 拉链表,并把全量表数据放到拉链表中,但是设置 拉链表中初始数据的end_date 字段为9999-99-99
第二步:根据增量表来更新 拉链表。
主要分为两部分: 1.把拉链表和增量表关联,关闭在增量表中出现的数据的end_date 为昨日日期,(即因为这些数据已经在增量表中出现,说明这些数据已经在昨天发生了变化,so 这些数据的end_date 要改变为昨日)
2.把增量表中的数据,union all 到拉链表中去,并设置 这些数据的end_date 字段为 999-99-99,(因为这些数据是最新变化的,即它们的状态是最新的,应该为9999-99-99)
===========================================
本文介绍数据仓库技术中拉链表相关的内容,包括其原理、设计、适用场景以及在Hive中的实现方式。
拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的所有变化的信息。即可以在所规定的时间粒度上体现数据完整的生命周期。
以下为一个拉链表的示例,存储账户基本信息和每条记录的生命周期,我们可以拿到每一个账户的历史上任意一天的状态和余额信息。
账户ID | 账户余额 | 账户状态 | 开始时间 | 结束时间 |
---|---|---|---|---|
1 | 100 | 正常 | 2017-12-31 | 2018-01-01 |
2 | 200 | 正常 | 2017-12-31 | 2018-01-01 |
3 | 500 | 正常 | 2017-12-31 | 2018-01-01 |
1 | 900 | 正常 | 2018-01-01 | 2018-03-01 |
3 | 0 | 注销 | 2018-01-31 | 9999-99-99 |
1 | 500 | 正常 | 2018-03-01 | 2018-03-20 |
1 | 300 | 正常 | 2018-03-20 | 9999-99-99 |
2 | 800 | 正常 | 2018-01-01 | 9999-99-99 |
在数据仓库的数据模型设计中,常常会遇到下列情况:
针对于这些情况,一般来说有以下几种方案可选:
下面我们来针对以上三个方案逐个分析
方案1:
方案2:
方案3:
接下来通过一个实例来简述一下应该如何设计拉链表
首先,针对于某账户信息表,在2018年1月1日的信息如下表(为了简化设计,这里增加了信息变更时间UPDATE_DATE):
账户ID | 账户余额 | 账户状态 | UPDATE_DATE |
---|---|---|---|
1 | 100 | 正常 | 2018-01-01 |
2 | 200 | 正常 | 2018-01-01 |
3 | 500 | 正常 | 2018-01-01 |
由此表我们可以得到以下拉链表,开始时间和结束时间表示数据的生命周期,结束时间9999-99-99表示此条数据为当前时间的数据:
账户ID | 账户余额 | 账户状态 | 开始时间 | 结束时间 |
---|---|---|---|---|
1 | 100 | 正常 | 2018-01-01 | 9999-99-99 |
2 | 200 | 正常 | 2018-01-01 | 9999-99-99 |
3 | 500 | 正常 | 2018-01-01 | 9999-99-99 |
接下来,在2018年1月2日做数据采集时,采集到了UPDATE_DATE为2018-01-02的以下数据:
账户ID | 账户余额 | 账户状态 | UPDATE_DATE |
---|---|---|---|
1 | 600 | 正常 | 2018-01-02 |
2 | 0 | 注销 | 2018-01-02 |
4 | 100 | 正常 | 2018-01-02 |
通过两个表的对比可以得出,对于同一个账户ID来说,1号账户的账户余额发生变更变成了600,2号账户的余额发生变更变成了100,则我们可以根据这张表和上面的拉链表关联,得到新的拉链表:
账户ID | 账户余额 | 账户状态 | 开始时间 | 结束时间 |
---|---|---|---|---|
1 | 100 | 正常 | 2018-01-01 | 2018-01-02 |
2 | 200 | 正常 | 2018-01-01 | 2018-01-02 |
3 | 500 | 正常 | 2018-01-01 | 9999-99-99 |
1 | 100 | 正常 | 2018-01-02 | 9999-99-99 |
2 | 0 | 注销 | 2018-01-02 | 9999-99-99 |
4 | 100 | 正常 | 2018-01-02 | 9999-99-99 |
以此类推,我们可以查询到2018年1月1日之后的所有生命周期的数据,例如:
SELECT * FROM ACCOUNT_HIST WHERE END_DATE = '9999-99-99'
SELECT * FROM ACCOUNT_HIST WHERE START_DATE <= '2018-01-01' AND END_DATE >= '2018-01-01'
在关系型数据库中,因为做INSERT/UPDATE操作非常的方便,所以即使逐条将变化的数据与拉链表比较并且做变更,也是非常容易做到的。
当然考虑到性能原因,会借助一些ETL工具,把数据加载到ETL工具中做拉链的操作。
这些方式都已有很成熟的处理方式,本文不再赘述。
在现有大数据场景之下,很多公司会使用Hive作为数据仓库的底层架构,而Hive在大部分使用场景中又是以HDFS为底层存储的。而在现有的Hadoop版本中,HDFS只支持Append操作(据说Hadoop3之后的版本支持了Update,但是截止笔者发布此篇文章Hadoop的Stable版本仍然是2.9.1,所以Update操作待考证)。
基于这个前提,我们要在Hive上实现拉链表,就要需要通过一些技巧来更新全表的数据。
首先根据以上逻辑设计,我们必须通过某些方式获得以下两部分数据:
全量表的获取不用多说,对于增量表的获取,根据经验主要有以下几种方式:
下面我们来在Hive中分别建立全量表、增量表,为了简便默认已经有可以表示数据变化时间的UPDATE_DATE:
|
|
根据全量表的表结构,在Hive中创建拉链表:
|
|
初始化这个拉链表的HiveQL语句如下:
|
|
每日在获取增量表数据后,执行以下语句,即可得到当日的拉链表:
|
|
只要每天执行上面这个脚本,那么拉链表就可以每天按计划生成了。
因为在一个表里保存了所有历史快照,所以如果时间足够久的话,拉链表也会遇到查询性能问题,主要通过以下几种思路来优化查询: