1.背景
拉鏈表是什么,在數倉建立時候,一種重要的表數據處理方式,可以將數據結構於算法,類比於拉鏈表於數倉,旨在解決數倉建立里面的SCD需求,那么什么是SCD,就是緩慢變化維,隨着時間流逝,數據相對事實表發生緩慢變化。
SCD的常用處理方式有以下幾種:
-
保留原值
-
直接覆蓋
-
增加新屬性列
-
快照表
-
拉鏈表
本文主要講解拉鏈表來處理SCD的問題,其特點歸納以下,有以下幾種場景時候,可以使用拉鏈表。
1.表數據量較大,用全量表會占用很多存儲
2.表數據會有修改,用增量表,難以處理重復且修改數據
3.有回溯的需求,需要知道歷史某個時間點的全量數據
4.數據有修改,但是頻率和量不是很大比如只有百萬分之一有修改
2.拉鏈表處理理論
首先拉鏈表是一個全量表且不是分區表,為了達到前面描述的各種效果,必然需要一個中間表來做中間跳板,這個中間跳板表是一個分區表,數據是增量數據,增量內容包括修改和增加,即常常是create_time or update_time
落在當前天,對於拉鏈表需要增加兩個與原始數據沒有關系的兩個字段來標識數據開始時間和有效截至時間,在示例中,這兩個日期分別為start_date
和 end_date
,拉鏈表其處理方式主要有以下三種:初始化,每天更新數據,回滾數據。
2.1 初始化和新增數據
其每天的滾動方式如下圖:
初始化部分,是拉鏈全量表的開始時間,也奠定了回滾時候能夠回滾的最早時間,每天更新邏輯如上圖,新增數據會分為兩部分,一部分是每天新增的數據,對於當天分區里面有相同變化或者未變化的數據時候,分別修改對應的start_date
和 end_date
即可達到更新數據。
2.1 數據回滾
對於上面的更新邏輯,我們來考慮如何回滾數據,即回到歷史的某個時間點,對於拉鏈表來說是全量表,所以只有一個回滾即可。回滾策略可以根據回滾時間點和數據生成的start_date
和 end_date
,具體怎么回滾,我們來看下面的示意圖:
在end_date < rollback_date
的數據要保留,對於處理end_date ≥ rollback_date ≥ start_date
設置end_date
為9999-12-31
,對於回滾的結果,一般為了保持數據的完整性,可以將回滾的數據放在一個新的拉鏈臨時表中。
3.拉鏈表處理案例
對於數倉的常用分層DIM即維度層是拉鏈表的常用場景,下面有個例子拉看看拉鏈表怎么做新增和回滾。
用拉鏈表實現核心交易分析中DIM層商家維表,並實現該拉鏈表的回滾。
3.1 創建表並導入數據
其中商家維表結構如下:
--創建商家信息表(增量表 分區表)
drop table if exists ods.ods_trade_shops;
create table ods.ods_trade_shops(
`shopid` int COMMENT '商鋪ID',
`userid` int COMMENT '商鋪負責人',
`areaid` int COMMENT '區域ID',
`shopname` string COMMENT '商鋪名稱',
`shoplevel` int COMMENT '商鋪等級',
`status` int COMMENT '商鋪狀態',
`createtime` string COMMENT '創建日期',
`modifytime` string COMMENT '修改日期'
) COMMENT '商家信息表'
PARTITIONED BY (`dt` string)
row format delimited fields terminated by ',';
-- 創建商家信息維表
drop table if exists dim.dim_trade_shops;
create table dim.dim_trade_shops(
`shopid` int COMMENT '商鋪ID',
`userid` int COMMENT '商鋪負責人',
`areaid` int COMMENT '區域ID',
`shopname` string COMMENT '商鋪名稱',
`shoplevel` int COMMENT '商鋪等級',
`status` int COMMENT '商鋪狀態',
`createtime` string COMMENT '創建日期',
`modifytime` string COMMENT '修改日期',
`startdate` string COMMENT '生效起始日期',
`enddate` string COMMENT '失效結束日期'
) COMMENT '商家信息表';
導入以下測試數據:
/root/data/shop-2020-11-20.dat
100050,1,100225,WSxxx營超市,1,1,2020-06-28,2020-11-20 13:22:22
100052,2,100236,新鮮xxx旗艦店,1,1,2020-06-28,2020-11-20 13:22:22
100053,3,100011,華為xxx旗艦店,1,1,2020-06-28,2020-11-20 13:22:22
100054,4,100159,小米xxx旗艦店,1,1,2020-06-28,2020-11-20 13:22:22
100055,5,100211,蘋果xxx旗艦店,1,1,2020-06-28,2020-11-20 13:22:22
/root/data/shop-2020-11-21.dat
100057,7,100311,三只xxx鼠零食,1,1,2020-06-28,2020-11-21 13:22:22
100058,8,100329,良子xxx鋪美食,1,1,2020-06-28,2020-11-21 13:22:22
100054,4,100159,小米xxx旗艦店,2,1,2020-06-28,2020-11-21 13:22:22
100055,5,100211,蘋果xxx旗艦店,2,1,2020-06-28,2020-11-21 13:22:22
/root/data/shop-2020-11-22.dat
100059,9,100225,樂居xxx日用品,1,1,2020-06-28,2020-11-22 13:22:22
100060,10,100211,同仁xxx大健康,1,1,2020-06-28,2020-11-22 13:22:22
100052,2,100236,新鮮xxx旗艦店,1,2,2020-06-28,2020-11-22 13:22:22
load data local inpath '/root/data/shop-2020-11-20.dat' overwrite into table ods.ods_trade_shops partition(dt='2020-11-20');
load data local inpath '/root/data/shop-2020-11-21.dat' overwrite into table ods.ods_trade_shops partition(dt='2020-11-21');
load data local inpath '/root/data/shop-2020-11-22.dat' overwrite into table ods.ods_trade_shops partition(dt='2020-11-22');
3.2 拉鏈表初始化
假設將第一天數據作為歷史的所有數據
INSERT OVERWRITE TABLE dim.dim_trade_shops
SELECT shopid,
userid,
areaid,
shopname,
shoplevel,
status,
createtime,
modifytime,
CASE
WHEN modifytime IS NOT NULL THEN substr(modifytime, 0, 10)
ELSE substr(createtime, 0, 10)
END AS startdate,
'9999-12-31' AS enddate
FROM ods.ods_trade_shops
WHERE dt ='2020-11-20';
3.3 更新拉鏈表
對於增量表,一般的邏輯是,create_time
或者modifytime
的截取作為當天分區dt
,modifytime
大於等於create_time
,這里取前兩個
INSERT OVERWRITE TABLE dim.dim_trade_shops
SELECT shopid,
userid,
areaid,
shopname,
shoplevel,
status,
createtime,
modifytime,
CASE
WHEN modifytime IS NOT NULL THEN substr(modifytime, 0, 10)
ELSE substr(createtime, 0, 10)
END AS startdate,
'9999-12-31' AS enddate
FROM ods.ods_trade_shops
WHERE dt = '2020-11-21'
UNION ALL
SELECT b.shopid,
b.userid,
b.areaid,
b.shopname,
b.shoplevel,
b.status,
b.createtime,
b.modifytime,
b.startdate,
CASE
WHEN a.shopid IS NOT NULL
AND b.enddate ='9999-12-31' THEN date_add('2020-11-21', -1)
ELSE b.enddate
END AS enddate
FROM
(SELECT *
FROM ods.ods_trade_shops
WHERE dt='2020-11-21') a
RIGHT JOIN dim.dim_trade_shops b ON a.shopid = b.shopid;
加載拉鏈表的腳本如下:
dim_load_shops.sh
#!/bin/bash
source /etc/profile
if [ -n "$1" ]
then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
INSERT OVERWRITE TABLE dim.dim_trade_shops
SELECT shopid,
userid,
areaid,
shopname,
shoplevel,
status,
createtime,
modifytime,
CASE
WHEN modifytime IS NOT NULL THEN substr(modifytime, 0, 10)
ELSE substr(createtime, 0, 10)
END AS startdate,
'9999-12-31' AS enddate
FROM ods.ods_trade_shops
WHERE dt = '$do_date'
UNION ALL
SELECT b.shopid,
b.userid,
b.areaid,
b.shopname,
b.shoplevel,
b.status,
b.createtime,
b.modifytime,
b.startdate,
CASE
WHEN a.shopid IS NOT NULL
AND b.enddate ='9999-12-31' THEN date_add('$do_date', -1)
ELSE b.enddate
END AS enddate
FROM
(SELECT *
FROM ods.ods_trade_shops
WHERE dt='$do_date') a
RIGHT JOIN dim.dim_trade_shops b ON a.shopid = b.shopid;
"
hive -e "$sql"
可以執行此腳本來加載2020-12-22
的數據,sh dim_load_shops.sh 2020-12-22
3.4 回滾拉鏈表到某一時間點
先創建一個臨時表,tmp.shops_tmp
用來放回滾的數據
DROP TABLE IF EXISTS tmp.shops_tmp;
CREATE TABLE IF NOT EXISTS tmp.tmp_shops AS
SELECT shopid,
userid,
areaid,
shopname,
shoplevel,
status,
createtime,
modifytime,
startdate,
enddate
FROM dim.dim_trade_shops
WHERE enddate < '2020-11-21'
UNION ALL
SELECT shopid,
userid,
areaid,
shopname,
shoplevel,
status,
createtime,
modifytime,
startdate,
'9999-12-31' AS enddate
FROM dim.dim_trade_shops
WHERE startdate <= '2020-11-21'
AND enddate >= '2020-11-21';
INSERT OVERWRITE TABLE dim.dim_trade_shops
SELECT *
FROM tmp.tmp_shops;
回滾腳本和更新腳本類似,只要更新其中的sql即可,這里不再重復。
吳邪,小三爺,混跡於后台,大數據,人工智能領域的小菜鳥。
更多請關注