人們普遍認為,在資料倉儲和商業智慧中,維度模型事給使用者顯示資訊的首選結構,它更易於理解和使用。又因為大資料和Hadoop的出現,允許了資料的高冗餘,維度建模便越發多公司使用。
上一節《2 - 到底哪種數倉設計模型更合適》中,我們簡單瞭解了業界用的最多的四種數倉建模方法。
本節我們繼續整合《阿里巴巴大資料之路》一書中提出的維度建模相關概念和方法,一步一步去踐行維度建模方法論,實踐的最後有相應的資料下載。
注:本節只作為demo,目的只為說明維度建模的實施過程,所以暫時不考慮實時資料加工。
劃分資料域
資料域是指面向業務分析,將業務過程或者維度進行抽象的集合。業務過程可以概括為個個不可拆分的行為事件,如下單、支付、退款。為保障整個體系的生命力,資料域需要抽象提煉,並且長期維護和更新,但不輕易變動。在劃分資料域時,既能涵蓋當前所有的業務需求,又能在新業務進入時無影響地被包含進已有的資料域中或者擴充套件新的資料域。 ------ 《阿里巴巴大資料之路》
資料域,通常是聯絡較為緊密的業務動作的集合,也就是說,資料域是針對事實表劃分的。
劃分方法
主題域的確定必須由終端使用者和資料倉儲的設計人員共同完成的, 而在劃分主題域時,大家的切入點不同可能會造成一些爭論、重構等的現象,考慮的點可能會是下方的某些方面:
1、按照業務或業務過程劃分:比如一個靠銷售廣告位置的入口網站主題域可能會有廣告域,客戶域等,而廣告域可能就會有廣告的庫存,銷售分析、內部投放分析等主題;
2、根據需求方劃分:比如需求方為財務部,就可以設定對應的財務主題域,而財務主題域裡面可能就會有員工工資分析,投資回報比分析等主題;
3、按照功能或應用劃分:比如微信中的朋友圈資料域、群聊資料域等,而朋友圈資料域可能就會有使用者動態資訊主題、廣告主題等;
4、按照部門劃分:比如可能會有運營域、技術域等,運營域中可能會有工資支出分析、活動宣傳效果分析等主題;
總而言之,切入的出發點邏輯不一樣,就可以存在不同的劃分邏輯。在建設過程中可採用迭代方式,不糾結於一次完成所有主題的抽象,可先從明確定義的主題開始,後續逐步歸納總結成自身行業的標準模型。
典型的電商行業資料域劃分如下:
資料域
|
業務過程
|
買家域和賣家域
|
註冊、登入、裝修、開店、關店等
|
商品域
|
釋出、上架、下架、重發、SKU庫存管理等
|
交易域
|
加購、下單、支付、確認收貨、退款等
|
事件域
|
曝光、瀏覽、點選、滑動等
|
營銷活動域
|
限時購、秒殺、滿減、團購、砍價助力等
|
互動域
|
評論、發帖、回帖等
|
倉儲配送域
|
商品採購、發貨、入庫、物流資訊跟蹤等
|
財務域
|
買家賬戶、賣家賬戶、平臺賬戶、推廣賬戶等
|
售後域
|
退款退貨、申訴、投訴、糾紛等
|
直播域
|
直播間開通、登出、點贊、彈幕、打賞、關注等
|
我們圍繞著電商的基本三要素:人、貨、場,細分和展開業務過程,在傳統業務的基礎上,增加了直播電商域。
匯流排矩陣
匯流排矩陣是根據事件域作維度,把業務過程和相關的維度描述出來,形成矩陣表。如:
域英文縮寫:buyer/saller
|
公共維度
|
||||||||
資料域
|
業務過程
|
日期
|
買家
|
賣家
|
店鋪
|
商品
|
訂單
|
活動
|
地區
|
買家域和賣家域
|
註冊
|
√
|
√
|
√
|
×
|
×
|
×
|
×
|
×
|
買家域和賣家域
|
登入
|
√
|
√
|
√
|
×
|
×
|
×
|
×
|
×
|
買家域和賣家域
|
裝修
|
√
|
×
|
√
|
√
|
×
|
×
|
×
|
×
|
買家域和賣家域
|
開店
|
√
|
×
|
√
|
√
|
×
|
×
|
×
|
×
|
買家域和賣家域
|
關店
|
√
|
×
|
√
|
√
|
×
|
×
|
×
|
×
|
買家域和賣家域
|
收藏
|
√
|
√
|
√
|
√
|
×
|
×
|
×
|
×
|
數倉架構
以下是數倉模型架構,每個公司都不要求一樣,所以請按照實際需求建層。
- ODS:Operational Data Store,運算元據層,在結構上其與源系統的增量或者全量資料基本保持一致。它相當於一個資料準備區,同時又承擔著基礎資料的記錄以及歷史變化。
- CDM:Common Data Model,公共維度模型層,又細分為DWD和DWS。它的主要作用是完成資料加工與整合、建立一致性維度、構建可複用的面向分析和統計的明細事實表以及彙總公共粒度的指標。
- DWD:Data Warehouse Detail,明細資料層。
- DWS:Data Warehouse Summary,彙總資料層。
- ADS:Application Data Service,應用資料層。
該資料分類架構在ODS層分為三部分:資料準備區、離線資料和準實時資料區。在進入到CDM層後,由以下幾部分組成:
- 公共維度層DIM:基於維度建模理念思想,建立整個企業的一致性維度。
- 明細粒度事實層DWD:以業務過程為建模驅動,基於每個具體業務過程的特點,構建最細粒度的明細層事實表。您可以結合企業的資料使用特點,將明細事實表的某些重要維度屬性欄位做適當的冗餘,即寬表化處理。
- 公共彙總粒度事實層DWS:以分析的主題物件為建模驅動,基於上層的應用和產品的指標需求,構建公共粒度的彙總指標事實表,以寬表化手段來物理化模型。
資料域,主要是用在CDM層中。
物理實現
以下,我們根據一個需求來實現從dws到dws到ads的表的物理設計,完整的pdm檔案會在最後放出。
求所有商品最近三十天的瀏覽人數和交易金額
資料域:商品域
資料粒度:商品
維度:商品
事實:支付和商品瀏覽
DIM 層設計
商品維度表
drop table dim.dim_item; /*==============================================================*/ /* Table: 商品維度表 */ /*==============================================================*/ create table dim.dim_item ( item_id bigint comment '商品ID', item_name string comment '商品名稱', img_url string comment '商品圖片連結', properties Map<string,string> comment '商品屬性(顏色、尺寸、尺碼)', status bigint comment '商品狀態', price bigint comment '商品價格', class_id_1 bigint comment '一級類目ID', class_id_1_name string comment '一級類目名稱', class_id_2 bigint comment '二級類目ID', class_id_2_name string comment '二級類目名稱', class_id_3 bigint comment '三級類目ID', class_id_3_name string comment '三級類目名稱', brand_id bigint comment '品牌ID', brand_name string comment '品牌名稱', create_tm string comment '建立時間 (yyyy-MM-dd hh:mm:ss)', chk_tm string comment '稽核時間 (yyyy-MM-dd hh:mm:ss)', seller_id bigint comment '賣家ID', saller_name string comment '賣家名稱', shop_id bigint comment '店鋪ID', shop_name string comment '店鋪名稱', is_self_support bigint comment '是否店鋪自營', supplier_id bigint comment '供應商ID', supplier_name string comment '供應商名稱', deli_country_id bigint comment '發貨國家ID', deli_country_name string comment '發貨國家名稱', deli_province_id bigint comment '發貨省ID', deli_province_name string comment '發貨省名稱', deli_city_id bigint comment '發貨市ID', deli_city_name string comment '發貨市名稱', deli_area_id bigint comment '發貨區ID', deli_area_name string comment '發貨區名稱', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', dw_source_sys string comment '數倉-來源系統縮寫', dw_source_tabs string comment '數倉-來源表列表(系統ID1.表名1,系統ID2.表名2... ...)', constraint PK_DIM_ITEM primary key (item_id) ) comment '商品維度表-全站' partitioned by (parent_id bigint comment '母商品ID');
DWD層設計
1. 新建【交易域】的訂單多事務事實表,包含下單和支付事實。
drop table dwd.dwd_sale_order_fct; /*==============================================================*/ /* Table: 訂單交易多事務事實表(下單、支付,當前事務事實需要置零處理)*/ /*==============================================================*/ create table dwd.dwd_sale_order_fct ( id bigint comment 'ID', order_id bigint comment '訂單號', big_order_id bigint comment '大訂單ID', sku_id bigint comment 'SKUID', sku_name string comment 'SKU名稱', item_id bigint comment '商品ID', parent_id bigint comment '母商品ID', item_name string comment '商品名稱', class_id_1_name string comment '商品一級類目名稱', class_id_2_name string comment '商品二級類目名稱', class_id_3_name string comment '商品三級類目名稱', item_price bigint comment '商品價格', buyer_id bigint comment '買家ID', buyer_name string comment '買家名稱', saller_id bigint comment '賣家ID', saler_name string comment '賣家名稱', shop_id bigint comment '店鋪ID', shop_name string comment '店鋪名稱', order_tm string comment '下單時間 (yyyy-MM-dd hh:mm:ss)', order_num bigint comment '下單數量', order_amt bigint comment '下單金額', share_order_amt bigint comment '下單分攤金額', discount_order_amt bigint comment '下單優惠金額', pay_tm string comment '支付時間(yyyy-MM-dd hh:mm:ss)', pay_num bigint comment '支付數量', buy_amt bigint comment '支付金額', share_buy_amt bigint comment '支付分攤金額', discount_buy_amt bigint comment '支付優惠金額', share_ship_amt bigint comment '運費分攤', rev_address_id bigint comment '收貨地址行政區域', ship_address_door_num string comment '收貨地址門牌號', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', dw_source_sys string comment '數倉-來源系統縮寫', dw_source_tabs string comment '數倉-來源表列表(系統ID1.表名1,系統ID2.表名2... ...)', constraint PK_DWD_SALE_ORDER_FCT primary key (id) ) comment '訂單交易多事務事實表(下單、支付,當前事務事實需要置零處理)' partitioned by (dt string comment '業務日期 (yyyy-MM-dd)');
2. 新建【事件域】的頁面瀏覽事實表,裡面有商品詳情頁的瀏覽事實(可計算出商品的UV)
drop table dwd.dwd_event_page_view_fct; /*==============================================================*/ /* Table: 頁面瀏覽事實表 */ /*==============================================================*/ create table dwd.dwd_event_page_view_fct ( id bigint comment 'ID', device_id string comment '裝置唯一ID', last_page_id bigint comment '上一個頁面ID', page_id bigint comment '頁面ID', user_id bigint comment '使用者ID', shop_id bigint comment '店鋪ID', item_id bigint comment '商品ID', ip bigint comment 'IP', address_id bigint comment '地址ID', channel string comment '渠道', phone_model bigint comment '手機型號', phone_brand string comment '手機品牌', os_system string comment '作業系統', app_version string comment 'APP版本號', page_stay_tm bigint comment '頁面停留時長(毫秒)', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', dw_source_sys string comment '數倉-來源系統縮寫', dw_source_tabs string comment '數倉-來源表列表(系統ID1.表名1,系統ID2.表名2... ...)', constraint PK_DWD_EVENT_PAGE_VIEW_FCT primary key () ) comment '頁面瀏覽事實表 (開啟、隱藏)' partitioned by (dt string comment '業務日期 (yyyy-MM-dd)');
DWS層設計
關於彙總層的表建模應遵循以下的原則:
- 資料公用性比如,彙總的聚集表能否與他人公用?基於某個維度的聚集是否是資料分析或者報表中經常使用的?如果滿足這些情況,我們就有必要把明細資料沉澱到彙總表中。
- 不跨資料域,資料域是在較高層次上對資料進行分類聚集的抽象,如交易統一劃到交易域下,商品的新增、修改放到商品域下。
- 區分統計週期,表命名上要能說明資料的統計週期,如_1d 表示最近1天,_td 截止到當天,_nd 表示最近N天。
- 避免多個層級的資料應該避免將不同層級的資料放在一起,比如,如果存在7天和30天的事實,我們可以選擇用兩列存放7天和30天的事實,但是需要在列名和欄位註釋上說明清楚。同時我們也可以使用兩張表分別儲存不同統計週期的資料加以區分。
- 聚集是不跨越事實的,聚集是針對原始星型模型進行的彙總,為了獲取和查詢原始模型一致的結果,聚集的維度和度量必須與原始模型保持一致,因此聚集是不跨事實的。橫向鑽取(交叉探查)是針對多個事實基於一致性維度進行的分析,很多時候採用融合事實表,預先存放橫向鑽取的結果,從而提高查詢效能。因此融合事實表是一種匯出模式而不是聚集。
按照以上原則,我們設計出兩張dws表:【商品粒度交易彙總事實表】、【商品粒度流量彙總事實表】。
drop table dws.dws_sales_item_info; /*==============================================================*/ /* Table: 商品粒度交易彙總事實表 */ /*==============================================================*/ create table dws.dws_sales_item_info ( item_id bigint comment '商品ID', item_name string comment '商品名稱', class_id_1 bigint comment '一級類目ID', class_id_1_name string comment '一級類目名稱', class_id_2 bigint comment '二級類目ID', class_id_2_name string comment '二級類目名稱', class_id_3 bigint comment '三級類目ID', class_id_3_name string comment '三級類目名稱', pro_area string comment '產地', order_num_sum_1w bigint comment '自然周下單數量', order_amt_sum_1w bigint comment '自然周下單金額', share_order_amt_sum_1w bigint comment '自然周下單分攤金額', discount_order_amt_sum_1w bigint comment '自然周下單優惠金額', pay_num_sum_1w bigint comment '自然周支付數量', buy_amt_sum_1w bigint comment '自然周支付金額', share_buy_amt_sum_1w bigint comment '自然周支付分攤金額', discount_buy_amt_sum_1w bigint comment '自然周支付優惠金額', gmv_sum_1w bigint comment '自然周GMV', order_num_sum_1m bigint comment '自然月下單數量', order_amt_sum_1m bigint comment '自然月下單金額', share_order_amt_sum_1m bigint comment '自然月下單分攤金額', discount_order_amt_sum_1m bigint comment '自然月下單優惠金額', pay_num_sum_1m bigint comment '自然月支付數量', buy_amt_sum_1m bigint comment '自然月支付金額', share_buy_amt_sum_1m bigint comment '自然月支付分攤金額', discount_buy_amt_sum_1m bigint comment '自然月支付優惠金額', gmv_sum_1m bigint comment '自然月GMV', order_num_sum_1d bigint comment '最近一日下單數量', order_amt_sum_1d bigint comment '最近一日下單金額', share_order_amt_sum_1d bigint comment '最近一日下單分攤金額', discount_order_amt_sum_1d bigint comment '最近一日下單優惠金額', pay_num_sum_1d bigint comment '最近一日支付數量', buy_amt_sum_1d bigint comment '最近一日支付金額', share_buy_amt_sum_1d bigint comment '最近一日支付分攤金額', discount_buy_amt_sum_1d bigint comment '最近一日支付優惠金額', gmv_sum_1d bigint comment '最近一日GMV', order_num_sum_7d bigint comment '最近七日下單數量', order_amt_sum_7d bigint comment '最近七日下單金額', share_order_amt_sum_7d bigint comment '最近七日下單分攤金額', discount_order_amt_sum_7d bigint comment '最近七日下單優惠金額', pay_num_sum_7d bigint comment '最近七日支付數量', buy_amt_sum_7d bigint comment '最近七日支付金額', share_buy_amt_sum_7d bigint comment '最近七日支付分攤金額', discount_buy_amt_sum_7d bigint comment '最近七日支付優惠金額', gmv_sum_7d bigint comment '最近七日GMV', order_num_sum_30d bigint comment '最近三十日下單數量', order_amt_sum_30d bigint comment '最近三十日下單金額', share_order_amt_sum_30d bigint comment '最近三十日下單分攤金額', discount_order_amt_sum_30d bigint comment '最近三十日下單優惠金額', pay_num_sum_30d bigint comment '最近三十日支付數量', buy_amt_sum_30d bigint comment '最近三十日支付金額', share_buy_amt_sum_30d bigint comment '最近三十日支付分攤金額', discount_buy_amt_sum_30d bigint comment '最近三十日支付優惠金額', gmv_sum_30d bigint comment '最近三十日GMV', order_num_sum bigint comment '累積下單數量', order_amt_sum bigint comment '累積下單金額', share_order_amt_sum bigint comment '累積下單分攤金額', discount_order_amt_sum bigint comment '累積下單優惠金額', pay_num_sum bigint comment '累積支付數量', buy_amt_sum bigint comment '累積支付金額', share_buy_amt_sum bigint comment '支付分攤金額', discount_buy_amt_sum bigint comment '累積支付優惠金額', gmv_sum bigint comment '累積GMV', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', dw_source_sys string comment '數倉-來源系統縮寫', dw_source_tabs string comment '數倉-來源表列表(系統ID1.表名1,系統ID2.表名2... ...)', constraint PK_DWS_SALES_ITEM_INFO primary key () ) comment '商品粒度交易彙總事實表' partitioned by (dt string comment '業務日期 (yyyy-MM-dd)'); drop table dws.dws_event_item_info; /*==============================================================*/ /* Table: 商品粒度流量彙總事實表 */ /*==============================================================*/ create table dws.dws_event_item_info ( item_id bigint comment '商品ID', item_name string comment '商品名稱', class_id_1 bigint comment '一級類目ID', class_id_1_name string comment '一級類目名稱', class_id_2 bigint comment '二級類目ID', class_id_2_name string comment '二級類目名稱', class_id_3 bigint comment '三級類目ID', class_id_3_name string comment '三級類目名稱', pro_area string comment '產地', uv_sum_1w bigint comment '自然周瀏覽人數', pv_sum_1w bigint comment '自然周瀏覽次數', uv_sum_1m bigint comment '自然月瀏覽人數', pv_sum_1m bigint comment '自然月瀏覽次數', uv_sum_1d bigint comment '最近一日瀏覽人數', pv_sum_1d bigint comment '最近一日瀏覽次數', uv_sum_7d bigint comment '最近七日瀏覽人數', pv_sum_7d bigint comment '最近七日瀏覽次數', uv_sum_30d bigint comment '最近三十日瀏覽人數', pv_sum_30d bigint comment '最近三十日瀏覽次數', uv_sum bigint comment '累積瀏覽人數', pv_sum bigint comment '累積瀏覽次數', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', dw_source_sys string comment '數倉-來源系統縮寫', dw_source_tabs string comment '數倉-來源表列表(系統ID1.表名1,系統ID2.表名2... ...)', constraint PK_DWS_EVENT_ITEM_INFO primary key () ) comment '商品粒度流量彙總事實表' partitioned by (dt string comment '業務日期 (yyyy-MM-dd)');
ADS層設計
要滿足需求,必須要從交易域和事件域抽出兩張彙總事實表進行交叉探查才能滿足需求
drop table ads.ads_item_info_30d; /*==============================================================*/ /* Table: 商品最近三十日的成交和流量資訊 */ /*==============================================================*/ create table ads.ads_item_info_30d ( item_id bigint comment '商品ID', item_name string comment '商品名稱', uv bigint comment '瀏覽人數', pay_amt bigint comment '支付金額', dw_create_tm string comment '數倉-記錄建立時間 (yyyy-MM-dd hh:mm:ss)', dw_update_tm string comment '數倉-記錄更新時間 (yyyy-MM-dd hh:mm:ss)', constraint PK_ADS_ITEM_INFO_30D primary key () ) comment '商品最近三十日的成交和流量資訊'; -- 偽加工程式碼為: inert overwrite table ads.ads.ads_item_info_30d select a.item_id, a.item_name, sum(a.uv_sum_30d) as uv, -- 瀏覽人數 sum(b.pay_amt_sum_30d) as pay_amt, -- 支付金額 from_unixtime(unix_timestamp(),"yyyy-MM-dd HH:mm:ss") create_tm, from_unixtime(unix_timestamp(),"yyyy-MM-dd HH:mm:ss") update_tm from dws.dws_event_item_info a left join dws.dws_sales_item_info b on a.item_id = b.item_id and a.dt = b.dt where a.dt = '2021-06-01';
總結
數倉的構建,工作量最大的還是需求的整理和資料域的處理;其次才是CDM層的表加工,再次是如何才能讓開發人員按照一定的規則和共識生產和豐富CDM層。一般來說ADS取數從直接的上游DWS中取,如果DWS中沒有,要看這個需求是否經常會用,能否報表化,如果能就在DWS中建公共彙總,如果不能就直接從DWD甚至是ODS層出便可,但一定要防止煙囪式開發。
更多
完整的專案設計過程資料和pdm文件等資料連結:設計過程文件