基於MaxCompute的拉鍊表設計
背景資訊:
在資料倉儲的資料模型設計過程中,經常會遇到這樣的需求:
-
資料量比較大;
-
表中的部分欄位會被update,如使用者的地址,產品的描述資訊,訂單的狀態、手機號碼等等;
-
需要檢視某一個時間點或者時間段的歷史快照資訊。(比如,檢視某一個訂單在歷史某一個時間點的狀態,比如,檢視某一個使用者在過去某一段時間內,更新過幾次等等)
-
變化的比例和頻率不是很大,比如,總共有1000萬的會員,每天新增和發生變化的有10萬左右;如果對這邊表每天都保留一份全量,那麼每次全量中會儲存很多不變的資訊,對儲存是極大的浪費;
綜上所述:引入`拉鍊歷史表`,既能滿足反應資料的歷史狀態,又可以最大程度的節省儲存。
(備註:在阿里巴巴內部很大程度上是基於儲存換計算來提供開發的效率及易用性,因為在當今,儲存的成本遠低於CPU和記憶體。因此在阿里巴巴內部會採用快照的方式將每日的全量資料進行快照,同時也會通過極限儲存的方式,壓縮率高,在合適的場景下,約能壓縮為原始資料的1/30。)
Demo資料
以下只是demo如何在MaxCompute中實現拉鍊表,所以是基於一些假設:
-
同一天中同一訂單隻有一個狀態發生;
-
基於20150821及之前的資料並沒有同一個訂單有兩個狀態的最簡單場景模擬;
-
且資料來源在阿里雲RDS for Mysql中。且表明為orders。
20150821以及之前的歷史訂單資料:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
1
|
20150818
|
20150818
|
建立
|
2
|
20150818
|
20150818
|
建立
|
3
|
20150819
|
20150821
|
支付
|
4
|
20150819
|
20150821
|
完成
|
5
|
20150819
|
20150820
|
支付
|
6
|
20150820
|
20150820
|
建立
|
7
|
20150820
|
20150821
|
支付
|
8
|
20150821
|
20150821
|
建立
|
20150822訂單資料:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
1
|
20150818
|
20150822
|
支付
|
2
|
20150818
|
20150822
|
完成
|
6
|
20150820
|
20150822
|
支付
|
8
|
20150821
|
20150822
|
支付
|
9
|
20150822
|
20150822
|
建立
|
10
|
20150822
|
20150822
|
支付
|
20150823的訂單資料:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
1
|
20150818
|
20150823
|
完成
|
3
|
20150819
|
20150823
|
完成
|
5
|
20150819
|
20150823
|
完成
|
8
|
20150821
|
20150823
|
完成
|
11
|
20150823
|
20150823
|
建立
|
12
|
20150823
|
20150823
|
建立
|
13
|
20150823
|
20150823
|
支付
|
建立MaxCompute表
–ODS層:訂單的增量資料表,按天分割槽,存放每天的增量資料
CREATE TABLE ods_orders_inc_d
(
orderid BIGINT
,createtime STRING
,modifiedtime STRING
,o_status STRING
)
PARTITIONED BY (dt STRING)
LIFECYCLE 7;
–DW層:歷史資料拉鍊表,存放訂單的歷史狀態資料
CREATE TABLE dw_orders_his_d
(
orderid BIGINT COMMENT `訂單ID`
,createtime STRING COMMENT `訂單建立時間`
,modifiedtime STRING COMMENT `訂單修改時間`
,o_status STRING COMMENT `訂單修改時間`
,dw_start_date STRING COMMENT `訂單生命週期開始時間`
,dw_end_date STRING COMMENT `訂單生命週期結束時間`
);
實現思路
-
全量初始化:將2015-08-21及以前的全量歷史資料通過全量方式同步至ODS並刷進DW層。
-
增量更新:將2015-08-22、2015-08-23的全天增量資料以增量方式刷入下游資料。
全量初始化
-
建立節點任務:資料同步
-
選擇排程型別:手動排程
-
配置資料同步任務:Mysql:Orders–>ODPS:ods_orders_inc_d
-
where條件配置:modifiedtime <= `20150821`
-
分割槽值dt=20150821
提交排程系統,待資料同步任務執行成功後,再將ODS資料刷入DW。
建立SQL指令碼:
INSERT overwrite TABLE dw_orders_his_d
SELECT orderid,createtime,modifiedtime,o_status,createtime AS dw_start_date,`99991231` AS dw_end_date
FROM ods_orders_inc_d
WHERE dt = `20150821`;
資料如下:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
start_date
|
end_date
|
1
|
20150818
|
20150818
|
建立
|
20150818
|
99991231
|
2
|
20150818
|
20150818
|
建立
|
20150818
|
99991231
|
3
|
20150819
|
20150821
|
支付
|
20150819
|
99991231
|
4
|
20150819
|
20150821
|
完成
|
20150819
|
99991231
|
5
|
20150819
|
20150820
|
支付
|
20150819
|
99991231
|
6
|
20150820
|
20150820
|
建立
|
20150820
|
99991231
|
7
|
20150820
|
20150821
|
支付
|
20150820
|
99991231
|
8
|
20150821
|
20150821
|
建立
|
20150821
|
99991231
|
通過以上步驟可以將2015-08-21及以前的歷史全量資料一次性刷入DW和ODS中。
增量抽取並生成拉鍊表
-
建立工作流任務並選擇週期性排程。
-
依次拖入資料同步節點任務和SQL任務。
-
在資料同步任務中where條件配置為:modifiedtime=${bdp.system.bizdate}
-
目標表ods_orders_inc_d分割槽配置為dt=${bdp.system.bizdate}
-
配置SQL節點,且為資料同步節點的下游節點。
--通過DW歷史資料和ODS增量資料重新整理DW表
insert overwrite table dw_orders_his_d
SELECT a0.orderid, a0.createtime, a0.modifiedtime, a0.o_status, a0.dw_start_date, a0.dw_end_date
FROM (
-- 對orderid進行開窗然後按照生命週期結束時間倒序排,支援重跑
SELECT a1.orderid, a1.createtime, a1.modifiedtime, a1.o_status, a1.dw_start_date, a1.dw_end_date
, ROW_NUMBER() OVER (distribute BY a1.orderid,a1.createtime, a1.modifiedtime,a1.o_status sort BY a1.dw_end_date DESC) AS nums
FROM (
-- 用歷史資料與增量22日的資料進行匹配,當發現在22日新增資料中存在且end_date > 當前日期的就表示資料狀態發生過變化,然後修改生命週期
-- 修改昨日已經生命截止的資料並union最新增量資料到DW
SELECT a.orderid, a.createtime, a.modifiedtime, a.o_status, a.dw_start_date
, CASE
WHEN b.orderid IS NOT NULL AND a.dw_end_date > ${bdp.system.bizdate} THEN ${yesterday}
ELSE a.dw_end_date
END AS dw_end_date
FROM dw_orders_his_d a
LEFT OUTER JOIN (
SELECT *
FROM ods_orders_inc_d
WHERE dt = ${bdp.system.bizdate}
) b
ON a.orderid = b.orderid
UNION ALL
--2015-08-22的增量資料重新整理到DW
SELECT orderid, createtime, modifiedtime, o_status, modifiedtime AS dw_start_date
, `99991231` AS dw_end_date
FROM ods_orders_inc_d
WHERE dt = ${bdp.system.bizdate}
) a1
) a0
-- 開視窗後對某個訂單中生命週期為`9999-12-31`的取值並寫入,防止重跑資料情況。
WHERE a0.nums = 1
order by a0.orderid,a0.dw_start_date;
備註:測試執行的時候,選擇業務日期為20150822。也可以通過補資料方式,直接把20150822和20150823兩天的增量資料刷入DW中。上面SQL中${yesterday}為自定義變數,其賦值為${yyyymmdd-1}
通過如上方式將20150822的增量資料刷入DW,如下所示:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
start_date
|
end_date
|
1
|
20150818
|
20150818
|
建立
|
20150818
|
20150821
|
1
|
20150818
|
20150822
|
支付
|
20150822
|
99991231
|
2
|
20150818
|
20150818
|
建立
|
20150818
|
20150821
|
2
|
20150818
|
20150822
|
完成
|
20150822
|
99991231
|
3
|
20150819
|
20150821
|
支付
|
20150819
|
99991231
|
4
|
20150819
|
20150821
|
完成
|
20150819
|
99991231
|
5
|
20150819
|
20150820
|
支付
|
20150819
|
99991231
|
6
|
20150820
|
20150820
|
建立
|
20150820
|
20150821
|
6
|
20150820
|
20150822
|
支付
|
20150822
|
99991231
|
7
|
20150820
|
20150821
|
支付
|
20150820
|
99991231
|
8
|
20150821
|
20150821
|
建立
|
20150821
|
20150821
|
8
|
20150821
|
20150822
|
支付
|
20150822
|
99991231
|
9
|
20150822
|
20150822
|
建立
|
20150822
|
99991231
|
10
|
20150822
|
20150822
|
支付
|
20150822
|
99991231
|
通過同樣的方式將2015-08-23日的資料增量輸入DW,其結果為:
訂單ID
|
建立時間
|
修改時間
|
狀態
|
start_date
|
end_date
|
1
|
20150818
|
20150818
|
建立
|
20150818
|
20150821
|
1
|
20150818
|
20150822
|
支付
|
20150822
|
20150822
|
1
|
20150818
|
20150823
|
完成
|
20150823
|
99991231
|
2
|
20150818
|
20150818
|
建立
|
20150818
|
20150821
|
2
|
20150818
|
20150822
|
完成
|
20150822
|
99991231
|
3
|
20150819
|
20150821
|
支付
|
20150819
|
20150822
|
3
|
20150819
|
20150823
|
完成
|
20150823
|
99991231
|
4
|
20150819
|
20150821
|
完成
|
20150819
|
99991231
|
5
|
20150819
|
20150820
|
支付
|
20150819
|
20150822
|
5
|
20150819
|
20150823
|
完成
|
20150823
|
99991231
|
6
|
20150820
|
20150820
|
建立
|
20150820
|
20150821
|
6
|
20150820
|
20150822
|
支付
|
20150822
|
99991231
|
7
|
20150820
|
20150821
|
支付
|
20150820
|
99991231
|
8
|
20150821
|
20150821
|
建立
|
20150821
|
20150821
|
8
|
20150821
|
20150822
|
支付
|
20150822
|
20150822
|
8
|
20150821
|
20150823
|
完成
|
20150823
|
99991231
|
9
|
20150822
|
20150822
|
建立
|
20150822
|
99991231
|
10
|
20150822
|
20150822
|
支付
|
20150822
|
99991231
|
11
|
20150823
|
20150823
|
建立
|
20150823
|
99991231
|
12
|
20150823
|
20150823
|
建立
|
20150823
|
99991231
|
13
|
20150823
|
20150823
|
支付
|
20150823
|
99991231
|
如何使用拉鍊表
-
檢視某一天的全量歷史快照資料。
SELECT *
FROM dw_orders_his_d
WHERE dw_start_date <= `20150822`
AND dw_end_date >= `20150822`
ORDER BY orderid
LIMIT 10000;
-
取一段時間的變化記錄集合,如在20150822-20150823變化的記錄。
SELECT *
FROM dw_orders_his_d
WHERE dw_start_date <= `20150823`
AND dw_end_date >= `20150822`
ORDER BY orderid
LIMIT 10000;
-
檢視某一訂單歷史變化情況。
SELECT *
FROM dw_orders_his_d
WHERE orderid = 8
ORDER BY dw_start_date;
-
取最新的資料。
SELECT *
FROM dw_orders_his_d
WHERE dw_end_date = `99991231`
關於基於歷史拉鍊表回滾某一天或一段時間內的資料,還是一個相對比較複雜的話題,這個可以下載再談。
相關文章
- 資料倉儲之拉鍊表設計
- MaxCompute表設計最佳實踐
- 資料倉儲之拉鍊表
- 拉鍊表的建立、查詢和回滾
- 增量表及拉鍊表,你懂了嗎?
- 基於shiro RBAC的表設計
- 深入講解拉鍊表,還怕面試官問?面試
- 基於MaxCompute的數倉資料質量管理
- 基於 Formily 的表單設計器實現原理分析 ORM
- 基於MaxCompute InformationSchema進行血緣關係分析ORM
- 【資料倉儲】全量表、快照表、增量表、拉鍊表、維度表、實體表、事實表
- 基於Vue,ElementUI開發的一款表單設計器VueUI
- 關於c語言單項鍊表尾新增C語言
- 基於MaxCompute構建企業使用者畫像
- 基於原型的角色設計方法原型
- 基於 esbuild 的 universal bundler 設計UI
- 736【畢設課設】基於51微控制器的計程車計價器打表儀器設計
- js 單項鍊表JS
- 基於UDP程式設計UDP程式設計
- 基於.NET架構的樹形動態報表設計與應用架構
- 基於FPGA的DDS研究與設計FPGA
- 基於MATLAB的水果分級設計Matlab
- 基於ATmega8的測速計設計
- 實現單項鍊表
- 聊聊基於敏捷的度量指標設計敏捷指標
- 基於原型的遊戲角色設計方法原型遊戲
- 基於promise的阻塞式佇列設計Promise佇列
- iOS中基於協議的路由設計iOS協議路由
- 基於SpringCloud的微服務架構設計SpringGCCloud微服務架構
- 基於RBAC的許可權設計模型模型
- 通用查詢設計思想(2)- 基於ADO.Net的設計
- 基於相關畢業設計論文下載基於WEB,基於java基於JSPWebJavaJS
- 非同步程式設計:基於事件的非同步程式設計模式(EAP)非同步程式設計事件設計模式
- 基於位運算的許可權設計
- 基於Verilog的陣列乘法器設計陣列
- 基於 Angular Material 的 Data Grid 設計實現Angular
- 基於Java反射的定時任務設計Java反射
- 基於php的公司企業網站設計PHP網站