背景:在業務數據中存在會改變狀態的數據,需要做歷史分析,比如訂單狀態、登錄位置等。
問題:如何找到歷史某一天的所有主體的狀態。
針對上述問題有三種解決方案:
- 每天保存全量數據快照:每天都做數據全量備份,可直接查詢歷史某個節點的全量數據。
- 每天只做增量數據抽取:查詢的時候需要按用戶分組並過濾更新時間最近的數據。
- 每天做增量數據且完成拉鍊:查詢指定開始和結束時間即可。
上述三種方案的利弊:
- 全量數據不夠合理,當數據變化少數據量大時資源浪費,但實現簡單,使用方便。
- 只做增量抽取使用複雜,不夠直觀。
- 拉鍊:數據佔用空間只是左右數據變化的歷史不會重複存儲,使用邏輯簡單。
綜上所述,在數據倉庫建設的過程中如果需要保存數據的歷史狀態可考慮拉鍊表,下面具體介紹拉鍊表是如何保存歷史且佔用空間較少的。
下面是一張訂單明細表:訂單號,創建時間,更改時間,訂單狀態
001 2016-08-20 2016-08-20 創建
002 2016-08-20 2016-08-20 創建
003 2016-08-20 2016-08-20 創建
001 2016-08-20 2016-08-21 支付
002 2016-08-20 2016-08-21 完成
004 2016-08-21 2016-08-21 創建
001 2016-08-20 2016-08-22 完成
003 2016-08-20 2016-08-22 支付
004 2016-08-21 2016-08-22 支付
005 2016-08-22 2016-08-22 創建
拉鍊表通過添加狀態的開始時間,結束時間來進行狀態標記 start_date,end_date
我們希望看到拉鍊表數據爲:
001 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-20
001 2016-08-20 2016-08-21 支付 2016-08-21 2016-08-21
001 2016-08-20 2016-08-22 完成 2016-08-22 9999-12-31
002 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-20
002 2016-08-20 2016-08-21 完成 2016-08-21 9999-12-31
003 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-21
003 2016-08-20 2016-08-22 支付 2016-08-22 9999-12-31
004 2016-08-21 2016-08-21 創建 2016-08-21 2016-08-21
004 2016-08-21 2016-08-22 支付 2016-08-22 9999-12-31
005 2016-08-22 2016-08-22 創建 2016-08-22 9999-12-31
這樣我們在查看2016-08-21這一天所有訂單的狀態時可以把條件寫爲start_date<='2016-08-21' and end_date => '2016-08-21'
下面介紹如何實現拉鍊表,以Hive爲例:
第一步建表:
CREATE TABLE orders (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING
) row format delimited fields terminated by '\t'
CREATE TABLE ods_orders_inc (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING
) PARTITIONED BY (day STRING)
row format delimited fields terminated by '\t'
CREATE TABLE dw_orders_his (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING,
dw_start_date STRING,
dw_end_date STRING
) row format delimited fields terminated by '\t' ;
第二步歷史數據全量接入:以時間節點爲2016-08-20
INSERT overwrite TABLE ods_orders_inc PARTITION (day = '2016-08-20')
SELECT orderid,createtime,modifiedtime,status
FROM orders
WHERE createtime < '2016-08-21' and modifiedtime <'2016-08-21';
--刷新至拉鍊表中
INSERT overwrite TABLE dw_orders_his
SELECT orderid,createtime,modifiedtime,status,
createtime AS dw_start_date,
'9999-12-31' AS dw_end_date
FROM ods_orders_inc
WHERE day = '2016-08-20';
如下結果:
select * from dw_orders_his;
OK
001 2016-08-20 2016-08-20 創建 2016-08-20 9999-12-31
002 2016-08-20 2016-08-20 創建 2016-08-20 9999-12-31
003 2016-08-20 2016-08-20 創建 2016-08-20 9999-12-31
第三步接入增量:2016-08-21
INSERT overwrite TABLE ods_orders_inc PARTITION (day = '2016-08-21')
SELECT orderid,createtime,modifiedtime,status
FROM orders
WHERE (createtime = '2016-08-21' and modifiedtime = '2016-08-21') OR modifiedtime = '2016-08-21';
select * from ods_orders_inc where day='2016-08-21';
OK
001 2016-08-20 2016-08-21 支付 2016-08-21
002 2016-08-20 2016-08-21 完成 2016-08-21
004 2016-08-21 2016-08-21 創建 2016-08-21
第四步拉鍊增量與歷史數據:
邏輯解析:
- 1)判斷歷史數據是否更新狀態,如果更新則將end_date改爲前一天時間,無更新不用變
- 2)將增量數據union並把end_date寫爲「9999-12-31」表示爲正在執行狀態
- 3)重寫原拉鍊表
DROP TABLE IF EXISTS dw_orders_his_tmp;
CREATE TABLE dw_orders_his_tmp AS
SELECT orderid,
createtime,
modifiedtime,
status,
dw_start_date,
dw_end_date
FROM (
//判斷失效值
SELECT a.orderid,
a.createtime,
a.modifiedtime,
a.status,
a.dw_start_date,
CASE WHEN b.orderid IS NOT NULL AND a.dw_end_date > '2016-08-21' THEN '2016-08-20' ELSE a.dw_end_date END AS dw_end_date
FROM dw_orders_his a
left outer join (SELECT * FROM ods_orders_inc WHERE day = '2016-08-21') b
ON (a.orderid = b.orderid)
UNION ALL
//判斷有效值
SELECT orderid,
createtime,
modifiedtime,
status,
modifiedtime AS dw_start_date,
'9999-12-31' AS dw_end_date
FROM ods_orders_inc
WHERE day = '2016-08-21'
) x
ORDER BY orderid,dw_start_date;
INSERT overwrite TABLE dw_orders_his
SELECT * FROM dw_orders_his_tmp;
按操作繼續完成2016-08-22日的新增數據拉鍊可以看到如下結構:
select * from dw_orders_his;
OK
001 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-20
001 2016-08-20 2016-08-21 支付 2016-08-21 2016-08-21
001 2016-08-20 2016-08-22 完成 2016-08-22 9999-12-31
002 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-20
002 2016-08-20 2016-08-21 完成 2016-08-21 9999-12-31
003 2016-08-20 2016-08-20 創建 2016-08-20 2016-08-21
003 2016-08-20 2016-08-22 支付 2016-08-22 9999-12-31
004 2016-08-21 2016-08-21 創建 2016-08-21 2016-08-21
004 2016-08-21 2016-08-22 支付 2016-08-22 9999-12-31
005 2016-08-22 2016-08-22 創建 2016-08-22 9999-12-31
下一篇討論拉鍊的其他實現方式和效率問題。