Hive中如何正确的使用拉链表
1 拉链表的使用场景
在数据仓库的模型设计的过程中,通常我们会碰到那种非常大的业务基础信息表,如用户表;
假如一个用户表有10亿条记录,50个列,就算使用orc压缩,但张表的存储也会超过100G,如果同步到Hive中按HDFS的默认备份,那就是300G,这样对磁盘的消耗也是非常大的。
假设该表的某些字段在业务端会产生update操作,但是每次update的字段就那么1到2个,其它字段不变,那么这些变化不频繁的维度字段被称为
缓慢渐变维
,而且相同id的变update频率很小,每天update的记录只占全表记录的很小一部分,如1/20000,那么此时有3种同步方案:1、 每日全量覆盖的方式
2、每日全量切片的方式
3、拉链表
方式1:简单易操作,但是不支持保留历史数据
方式2:简单易操作,且能保证历史,但是会导致数仓存储大量的重复冗余数据,占用大量磁盘空间
方式3:拉链表能保证每条记录的生命周期的start-date和end-date,既能保证历史数据,同时也优化了冗余存储;
2 拉链表特点
假如业务那边有一个User表,表结构如下
2020-07-01的数据
注册日期 | 用户id | 手机号码 |
---|---|---|
2020-07-01 | 001 | 111111 |
2020-07-01 | 002 | 222222 |
2020-07-01 | 003 | 333333 |
2020-07-02的数据
003的电话号码作了修改,从333333变成了323232,同时新增了一个用户004
注册日期 | 用户id | 手机号码 |
---|---|---|
2020-07-01 | 001 | 111111 |
2020-07-01 | 002 | 222222 |
2020-07-01 | 003 | 323232 |
2020-07-02 | 004 | 444444 |
2020-07-03的数据
002的电话号码作了修改,从222222变成了212121,同时新增了一个用户005
注册日期 | 用户id | 手机号码 |
---|---|---|
2020-07-01 | 001 | 111111 |
2020-07-01 | 002 | 212121 |
2020-07-01 | 003 | 323232 |
2020-07-02 | 004 | 444444 |
2020-07-03 | 005 | 555555 |
那么加入我们按照T+1的方式同步数据,那么拉链表在hive中存储样式应该如下:
start_date代表记录的生效起始时间,end_date代表记录失效时间
加入我们需要查询002在cal_date的对应的有效记录,可以使用start_date <= cal_date and end_date > cal_date
进行限定,
如果我们需要查询当前的有效数据,那么我们只需要按照where end_date = '9999-12-31'
来进行限定就okay了。
注册日期 | 用户id | 手机号码 | start_date | end_date |
---|---|---|---|---|
2020-07-01 | 001 | 111111 | 2020-07-01 | 9999-12-31 |
2020-07-01 | 002 | 222222 | 2020-07-01 | 2020-07-03 |
2020-07-01 | 002 | 212121 | 2020-07-03 | 9999-12-31 |
2020-07-01 | 003 | 333333 | 2020-07-01 | 2020-07-02 |
2020-07-01 | 003 | 323232 | 2020-07-02 | 9999-12-31 |
2020-07-02 | 004 | 444444 | 2020-07-02 | 9999-12-31 |
2020-07-03 | 005 | 555555 | 2020-07-03 | 9999-12-31 |
3 在Hive中实现拉链表
注意⚠️:在向大数据转型的趋势下,很多企业将Hive作为数据仓库的首选,但是Hive数据是基于HDFS的文件,只支持delete和insert操作,不支持update
。
- user
- user_update
- user_zip
整体思路:dw的新数据 = dw的旧数据+新增数据
-- ods的原始切片表
create table if not exists ods.user(reg_date date,user_id string,mobile string)
comment '用户信息表'
row format delimited fields terminated by '\t' lines terminated by '\n';
--ods的更新表,用来存储新增记录
--怎么定义新增?将所有字段concat、然后取md5值与ods的原始切片表进行比较
create table if not exists ods.user_update(reg_date date,user_id string,mobile string)
comment '用户信息新增表'
row format delimited fields terminated by '\t' lines terminated by '\n';
--dw的拉链表
create table if not exists dw.user_zip(reg_date date,user_id string,mobile string,start_date string ,end_date string)
comment '用户信息拉链表'
row format delimited fields terminated by '\t' lines terminated by '\n';
DW层同步代码为:
INSERT OVERWRITE TABLE user_zip
SELECT
*
FROM(
--将修改的数据进行生命周期的修改,
SELECT
a.reg_date,
a.user_id,
a.mobile,
a.start_date,
case
when a.end_date = '9999-12-31' and b.user_id is not null then '2020-07-02'
else a.end_date
end as end_date --说明通过user_id关联上了,在user_update表中也算今日的新增数据
FROM dw.user_zip a
LEFT JOIN ods.user_update b
ON a.user_id = b.user_id
UNION
--将新增的数据直接union进来
SELECT
reg_date,
user_id,
mobile,
'2020-07-02' as start_date,
'9999-12-31' as end_date
FROM ods.user_update
) fi_table
--最终将dw的数据每日全量覆盖
4 用Mysql来模拟hive的拉链表
- 由于Mysql中不支持Hive中的insert overwrite,所以用一张临时表进行代替,SQL如下:
create table if not exists user(reg_date varchar(24),user_id int ,mobile varchar(24)) engine = innodb default charset = utf8mb4;
create table if not exists user_update(reg_date varchar(24),user_id int ,mobile varchar(24)) engine = innodb default charset = utf8mb4;
create table if not exists user_his(reg_date varchar(24),user_id int ,mobile varchar(24),start_date varchar(24) ,end_date varchar(24)) engine = innodb default charset = utf8mb4;
######################2020-07-01###################
truncate table user;
truncate table user_update;
truncate table user_his;
insert into user values('2020-07-01',001,'111111'),('2020-07-01',002,'222222'),('2020-07-01',003,'333333');
insert into user_update select * from user;
truncate table user_his;
insert into user_his
select
*
from (
select
a.reg_date,
a.user_id,
a.mobile,
a.start_date as start_date,
if(b.user_id is not null and a.end_date ='9999-12-31','2020-07-01',a.end_date)
from user_his a
left join user_update b
on a.user_id = b.user_id
union
select
reg_date,
user_id,
mobile,
'2020-07-01' as start_date,
'9999-12-31' as end_date
from user_update
) tmp ;
######################2020-07-02###################
#模拟新增、修改数据,在新增表中,修改与新增都属于新增
delete from user_update where user_id= 001;
update user_update set mobile = '212121' where user_id = 002;
update user_update set mobile = '323232' where user_id = 003;
insert into user_update values('2020-07-02',004,'444444');
drop table if exists user_his_temp;
create table if not exists user_his_temp as select * from user_his;
truncate table user_his;
insert into user_his
select
*
from (
select
a.reg_date,
a.user_id,
a.mobile,
a.start_date as start_date,
if(b.user_id is not null and a.end_date ='9999-12-31','2020-07-02',a.end_date)
from user_his_temp a
left join user_update b
on a.user_id = b.user_id
union
select
reg_date,
user_id,
mobile,
'2020-07-02' as start_date,
'9999-12-31' as end_date
from user_update
) tmp ;