什么是拉链表,并通过hive进行实现
拉链表产生背景
在数据仓库的数据模型设计过程中,经常会遇到这样的需求:
1、数据量比较大;
2、表中的部分字段会被update,如用户的地址,产品的描述信息,订单的状态等等;
3、需要查看某一个时间点或者时间段的历史快照信息,比如,查看某一个订单在历史某一个时间点的状态,比如,查看某一个用户在过去某一段时间内,更新过几次等等;
4、变化的比例和频率不是很大,比如,总共有1000万的会员,每天新增和发生变化的有10万左右;
5、如果对这边表每天都保留一份全量,那么每次全量中会保存很多不变的信息,对存储是极大的浪费;
对于这种表有几种方案可选:
方案一:每天只留最新的一份,比如我们每天用Sqoop抽取最新的一份全量数据到Hive中。
方案二:每天保留一份全量的切片数据。
方案三:使用拉链表。
以上方案对比
方案一
这种方案就不用多说了,实现起来很简单,每天drop掉前一天的数据,重新抽一份最新的。
优点很明显,节省空间,一些普通的使用也很方便,不用在选择表的时候加一个时间分区什么的。
缺点同样明显,没有历史数据,先翻翻旧账只能通过其它方式,比如从流水表里面抽。
方案二
每天一份全量的切片是一种比较稳妥的方案,而且历史数据也在。
缺点就是存储空间占用量太大太大了,如果对这边表每天都保留一份全量,那么每次全量中会保存很多不变的信息,对存储是极大的浪费,这点我感触还是很深的…
当然我们也可以做一些取舍,比如只保留近一个月的数据?但是,需求是无耻的,数据的生命周期不是我们能完全左右的。
拉链表
拉链表在使用上基本兼顾了我们的需求。
首先它在空间上做了一个取舍,虽说不像方案一那样占用量那么小,但是它每日的增量可能只有方案二的千分之一甚至是万分之一。
其实它能满足方案二所能满足的需求,既能获取最新的数据,也能添加筛选条件也获取历史的数据。
所以我们还是很有必要来使用拉链表的。
拉链表概念
拉链表是一种数据模型,主要是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的所有变化的信息。拉链表可以避免按每一天存储所有记录造成的海量存储问题,同时也是处理缓慢变化数据(SCD2)的一种常见方式。
百度百科的解释:拉链表是维护历史状态,以及最新状态数据的一种表,拉链表根据拉链粒度的不同,实际上相当于快照,只不过做了优化,去除了一部分不变的记录,通过拉链表可以很方便的还原出拉链时点的客户记录。
拉链表算法
1、采集当日全量数据到ND(NowDay当日)表;
2、可从历史表中取出昨日全量数据存储到OD(OldDay上日)表;
3、两个表进行全字段比较,(ND-OD)就是当日新增和变化的数据,也就是当天的增量,用W_I表示;
4、两个表进行全字段比较,(OD-ND)为状态到此结束需要封链的数据,需要修改END_DATE,用W_U表示;
5、将W_I表的内容全部插入到历史表中,这些是新增记录,start_date为当天,而end_date为max值,可以设为’9999-12-31‘;
6、对历史表进行W_U部份的更新操作,start_date保持不变,而end_date改为当天,也就是关链操作,历史表(OD)和W_U表比较,START_DATE,END_DATE除外,以W_U表为准,两者交集将其END_DATE改成当日,说明该记录失效。
具体简单实例演示,订单表实例操作,
首先第一部准备一个基本订单表,导入xhsell里面,订单信息是
[[email protected] lalian]# vi orders.txt
接着在hive里面创建一个拉链数据库并且创建一个订单表单
hive> create database lalian;
hive> use lalian;
hive> create table orders(
> orderid int,
> createtime string,
> modifytime string,
> status string
> )
> row format delimited fields terminated by 't';
查看表详细情况,将文件插入进表单
hive>desc formatted orders;
[[email protected] lalian]# hdfs dfs -put ./orders.txt /user/hive/warehouse/lalian.db/orders
查看是否插入成功
接着来到了,修改添加情况表,订单表开始进入下一个流程
hive> create table ods_orders_inc(
> orderid int,
> createtime string,
> modifytime string,
> status string
> )
> partitioned by (day string)
> row format delimited fields terminated by 't';
接着插入数据,设置分区的意义在于之后进行覆盖时,不会将已经设置过的分区进行覆盖,方便进行拉链的操作,分区的日期可以根据要求来设置
hive> insert overwrite table ods_orders_inc partition(day='2021-11-20')
> select orderid,createtime,modifytime,status from orders;
最后开始用mysql语句进行拉链操作,重点
先说一下我的最简单的理解
比如没进行一下更新它的时间会来到第二天,整体分区时间肯定是后一天也就是2021-11-21,订单编号1有了新变化,它的修改日期是2021-11-21,那么它在最终表哪里的创建日期是不变的,修改日期肯定放生的变化,变为了2021-11-21,状态也变为了支付状态,那么开始时间就从2021-11-21开始呢,那么原本的数据的结束时间由9999-12-31发生了变化变为了当天修改变化的时间2021-11-21,而修改变化后的结束时间,在当天是没有变化的,则为9999-12-31,对于所有修改过的订单号都可以这样类推,而对于订单4新插入的数据可以直接塞进去,结束时间在当天没有发生变化,则为9999-12-31,像这种将前一天结束时间最为后一天的开始时间就类似于拉链的操作,下面会进行具体的SQL操作
初始表可以理解为
hive> create table dws_orders_his(
> orderid int,
> createtime string,
> modifytime string,
> status string,
> start_time string,
> end_time string
> )
> row format delimited fields terminated by 't';
最终表的最后两个字段意思,modifytime代表着修改时间,就类似于开始时间,而最后一个结束时间字段可以给定具体的时间如9999-12-31,可以方便后面进行具体操作
hive> insert overwrite table dws_orders_his
> select orderid,createtime,modifytime,status,modifytime,'9999-12-31' from ods_orders_inc
下面进行支付订单mysql操作
将21日orders表里的状态修改及新增的数据导入到 ods_orders_inc 分区为2021-11-21
hive> insert overwrite table ods_orders_inc partition(day='2021-11-21')
> select orderid,createtime,modifytime,status from orders
> where(createtime='2021-11-21'and modifytime='2021-11-21') or modifytime='2021-11-21';
-- 两过查询 合并 UNIN ALL --
-- 通过his left join inc 找出定单状态有更新的数据 --
-- 将inc 2021-11-21的数据添加end_time为9999-12-31后插入his表 --
逐帧解析一下
前三行就是为了最后进行排序用的;
left join左边的t1部分,按照下面代码和图片对照来说明一下
查询字段不用说明,在case when部分是首先修改的内容里面没有对于订单3的操作,所有是为null,那么条件里面就先去掉有空的数据,接着t1的end_time是原来是9999-12-31,在进行修改之后就会发生变化,就当他大于当前修改时间时,则会修改原来的9999为2021-11-21,如果不大于就还是默认原来的end_time,这部分就相当于执行完所有修改的数据的要求,最后实现的效果就把原来的his表格也就是前一天的数据进行了修改,但是当天的数据没有显示
最后进行union all,将当天的数据也拼接起来
后部分就是查询并且赋予两个新字段进行union all
hive> create table dws_his_20211121_tmp as
> select t.orderid,t.createtime,t.modifytime,
> t.status,t.start_time,t.end_time
> from (
> select t1.orderid,t1.createtime,t1.modifytime,t1.status,t1.start_time,
> case when t2.orderid is not null and t1.end_time>'2021-11-21' then '2021-11-21' else t1.end_time end end_time
> from dws_orders_his t1
> left join (select orderid from ods_orders_inc where day='2021-11-21') t2
> on t1.orderid=t2.orderid
> union all
> select orderid,createtime,modifytime,status,modifytime as start_time,'9999-12-31' as end_time
> from ods_orders_inc where day='2021-11-21')t
> order by t.orderid ,t.start_time;
实现效果如下
如果继续进行下一天的操作,比如进行完成操作
和上面一模一样只需要吧2021-11-21改为22即可
先对orders表进行插入
在进行分区
hive> insert overwrite table ods_orders_inc partition(day='2021-11-22')
> select orderid,createtime,modifytime,status from orders
> where (createtime='2021-11-22' and modifytime='2021-11-22') or modifytime='2021-11-22'
> ;
最后执行和一开始一样的操作
hive> create table dws_his_20211122_tmp as
> select t.orderid,t.createtime,t.modifytime,
> t.status,t.start_time,t.end_time
> from (
> select t1.orderid,t1.createtime,t1.modifytime,t1.status,t1.start_time,
> case when t2.orderid is not null and t1.end_time>'2021-11-22' then '2021-11-22' else t1.end_time end end_time
> from dws_orders_his t1
> left join (select orderid from ods_orders_inc where day='2021-11-22') t2
> on t1.orderid=t2.orderid
> union all
> select orderid,createtime,modifytime,status,modifytime as start_time,'9999-12-31' as end_time
> from ods_orders_inc where day='2021-11-22')t
> order by t.orderid ,t.start_time;
最终效果