【資料倉儲】|3 維度建模之維度表設計

X.Jan發表於2021-05-22
維度是看待事情發生的角度,是維度建模的基礎和靈魂。
 

維度設計基礎

基本概念

我們在維度建模中,把度量稱為事實,將環境稱為維度
舉個例子,在電商業務中有這麼個需求:
我需要統計昨日所有店鋪成交金額。這裡的維度就是:日期、店鋪;度量(也就是事實)是:成交金額。
所以延伸下來,電商業務中的基礎維度有:日期、店鋪、買家、賣家、商品、類目、地區等;而事實有成交金額、購買人數、購買件數等。
 
維度表的主鍵有兩種:代理鍵和業務主鍵(也叫自然鍵)。兩者區別在之前的文章《DataVault建模》已經解釋過。
 
維度屬性指的就是維度的列。一般是我們在資料分析時用到的過濾條件、分組、排序等,所以維度屬性越豐富,可以觀察的角度就越多。
如果從SQL查詢的角度上看,維度屬性通常是放在where和group by、sort by後的列。
 
 

設計方法

維度的設計過程,就是維度屬性的確定過程。
Kimball:“數倉的能力如何,跟維度屬性的深度和質量成正比。”
 
下面我們用商品維度表的設計為例對維度設計的方法進行詳細說明。
 

選擇維度或者新建維度

在建設維度表中,要保證其在數倉中的唯一性,也就是說只允許有一個商品維表。
 

確定維度主來源表

在此處一般指的就是ODS層(與業務系統表結構一樣)的商品表,如s_items_info,此表就是維度的主來源表。
 

確定相關維表

資料倉儲的設計遵循資料的高度整合原則。在確定主來源表後,還需要根據實際需求,擴充套件商品的相關資訊如:類目、所屬賣家、所屬店鋪等
 

確定維度屬性

在維度主來源表+相關維表的基礎欄位上,建立或補充維度屬性:
  • 儘可能地生成新的維度屬性
  • 儘可能給出一些包含文字描述的屬性,這些屬性不應該只有編碼,更應該是真正的文字。如一級類目ID,一級類目名稱
  • 某些特殊的度量(數字)有可能也能作為維度屬性。如商品單價,既在觀察商品價格段時可以作為維度,也可以在求平均商品價格時作為事實。(區分數值型欄位是維度還是度量的方法之一,就是看欄位內容列舉值的多寡,多很可能是度量;少很可能是維度,但不絕對)
  • 儘量沉澱出常用、公用的欄位。如商品狀態,需要通過上架時間判斷
 
 

維度的層次結構

維度是有層次的,也是反正規化的。拿商品維表舉例,
【商品維表】(商品ID,商品名稱,商品類目,一級類目ID,一級類目名稱,二級類目ID,二級類目名稱,三級類目ID,三級類目名稱,上架時間)
類目層次:一級類目 》二級類目 》三級類目
時間層次:年 》月 》季度 》周 》天
 
這種層次結構常用在什麼場景?資料鑽取。
什麼是資料鑽取?
資料鑽取分為上鑽(維度減少)和下鑽(維度增多)。簡單來說就是想點開年份看詳細的月份或者天資料,就叫下鑽;如果由每天的維度變為看季度、年維度,那就是上鑽。
 
常見的維度層次結構有以下幾個:日期,地址,類目等。
 
 

星型模型和雪花模型

我們只要細心看上面商品表,會發現其實如果我們把類目當成一個單獨的維度表抽離出來也是可行的。於是變成以下兩張表:
【商品維表】(商品ID,商品名稱,類目ID,上架時間)
【類目維表】(類目ID,一級類目ID,一級類目名稱,二級類目ID,二級類目名稱,三級類目ID,三級類目名稱)
這種方法在維度建模中是允許存在的,當然我們更建議把類目維表的資訊反規範化到商品維表中。這兩種方法有個專業名稱,叫星型模型和雪花模型。
星型模型和雪花模型中,都只有一個事實表,但是對於大多數業務而言都過於理想話,最後,由於事實表的增加,星型模型和雪花模型都會演化成最終的星座模型
星座模型裡會有多個事實表跟維度表公用。
 
 

一致性維度和交叉探查

 

交叉探查

 
不同資料域(後面章節詳細介紹,大概意思是不同業務模組的資料)的事實資料,根據同一個維度做合併的情況就叫交叉探查。
電商資料中最常見的是日誌域和交易域的交叉探查。
 
假設有兩張事實表:
【日誌域.使用者瀏覽商品行為事實表】(使用者ID,商品ID,渠道ID,行為發生時間)- A表,三個維度外來鍵
【交易域.使用者購買商品事實表】(使用者ID,商品ID,成交金額)- B表,兩個維度外來鍵
假設有兩張上架時間格式不一樣的商品維度表:
【商品維表A】(商品ID,商品名稱,上架時間(格式:yyyy-MM-dd HH:mm:ss))
【商品維表B】(商品ID,商品名稱,上架時間(格式:UNIX timestamp))
 
如:我想查詢所有上架時間大於 2021-05-20 16:12:30 的每個商品的PV、UV和成交金額。
用一條偽SQL舉例:
select
    商品ID
    ,pv
    ,uv
    ,成交金額
from (
    select
       商品ID -- 交叉探查,同一個維度
       ,count(*) as pv
       ,distinct(user_id) as uv
       from A
       group by 商品ID         -- 日誌域事實,維度上鑽為商品ID
) as X1
join (
    select 
      商品ID -- 交叉探查,同一個維度
      ,sum(成交金額) as 成交金額
      from B
      group by 商品ID          -- 交易域事實,維度上鑽為商品ID
) as X2 on xxx =  xxx
join 商品維表A/商品維表B on xxx = xxx -- 注意商品維表這個地方!
where 上架時間>'2021-05-20 16:12:30'
 
OK,要想交叉探查資料是對的,也就是說想要上面SQL資料無誤,前提必須是A表的商品和B表的商品維表屬性(上架時間格式)一致。
如果不一致,將會導致資料出錯,只能過濾掉部分資料。
如果覺得例子麻煩,那隻需要記住一點即可,要想交叉探查資料無誤,就一定要保證:一致性維度
 
 

一致性維度

Kimball的資料倉儲匯流排架構提供了種分解企業級資料倉儲規劃任務的合理方法,通過構建企業範圍內一致性維度和事實來構建匯流排架構。 —— 《阿里巴巴大資料之路》
意思是維度建模要求必須有一致性維度。換句話說維度是統一設計的,每個維度表都是唯一不重複的,要做到全域性通用。否則會導致資料查詢的時候不一致甚至錯誤。
如何才能保證有一致性維度呢?有三種方法:
  • 共享維度。每個維度全域性都唯一(下面的複雜就只要做到這一點就好)
  • 一致性上鑽。其中一個維度的維度屬性是另 一個維度的維度屬性的子集,且兩個維度的公共維度屬性結構和內容相同。比如商品維度和類目維度,其中類目維度的維度屬性是商品維度的維度屬性的子集,且有相同的維度屬性和維度屬性值。這樣基於類目維度進行不同業務過程的交叉探查也不會存在任何問題。
  • 交叉屬性 。兩個維度具有部分相同的維度屬性。比如在商品維度中具有類目屬性,在賣家維度中具有主營類目屬性,兩個維度具有相同的類目屬性,則可以在相同的類目屬性上進行不同業務過程的交叉探查。
 
 

維度設計高階主題

 

什麼維度需要整合

資料倉儲的四大特性裡面包含整合。維度的整合的過程可以概括為:將維度相關的維度屬性做到統一。
  • 來源系統多的情況下,表名、欄位名要統一。如A系統使用者ID是userid,B系統是user_id,維度表需要將這些統一
  • 公共程式碼和編碼值統一,如A系統男1,女0,B系統男M,女F;
  • 業務含義相同的表統一
    • 採用主從表方式,如商品維度可以拆成(商品主資訊維表 + 商品擴充套件資訊維表)
    • 統一到一張表,如果表欄位重合度比較低,會出現大量空值情況
    • 不合並,如果源表表結構實在差異太大,可以不合並
 

什麼維表需要拆分

當一張維度表中包含多個類別、加工邏輯十分困難、有部分維度屬性可以單獨處理或者不常用時,考慮將維度拆分。
無論是維表是分還是合,都需要從以下角度權衡:
  • 當業務變化時,模型是否容易擴充套件
  • 是否易用
  • 查詢效能問題
 
通常來說,拆分方法有以下幾種:
 

水平拆分——資料層面

我們假設航旅的商品和普通的商品,都屬於商品,都有商品價格、標題、型別、上架時間、類目等維度屬性,但是航旅的商品除了有這些公共屬性外,還有酒店、景點、門票、旅行等自己獨特的維度屬性。我們應該如何去設計維度?針對此問題,有兩種解決思路:
  • 方案1:提取航旅和普通商品的公共屬性作為建一個維表【商品維表】,然後建立一個子維度表,【航旅商品維表】(公共屬性,航旅特有屬性)
  • 方案2:只建一個維表,儲存所有屬性
當航旅類別的商品頻繁更新、變化較大或者業務關聯程度較低時,應當選擇方案1,建單獨的一張航旅商品維表進行維護。在大型系統中,基於這種思路,商品維表可能會被拆分成N個不同的子商品維表,而各個子維表的維度屬性必定會比【商品維表】多。
囉嗦一句:【航旅商品維表】是【商品維表】的子維度表,它只存航旅相關的商品。
 

垂直拆分——維度屬性層面

當某些維度屬性的來源表產出時間較早,而某些維度屬性的來表產出時間較晚;或者某些維度屬性的熱度高、使用頻繁,而某些維度屬性的熱度低、較少使用,都可以使用主從表垂直拆分。
 
 

歷史歸檔——無用資料層面

維度資料日積月累會有不少記錄是廢用的,我們應該給資料量大的維度表,新建一張歷史維度表做資料歸檔。但如何去識別不用的資料進行歷史歸檔?
《阿里巴巴大資料之路》中給出三個思路:
  • 將商品狀態為下架或刪除的且最近 31 天未更新的商品歸檔至歷史庫,不推薦
  • 資料倉儲自定義歸檔,不推薦
  • 通過binlog merge的delete標記(邏輯刪除)進行歸檔,推薦
 
 

維度的變化

 

緩慢變化維

資料倉儲中另一重要的特點是——反映歷史變化。緩慢變化維度,這裡的緩慢是跟(快速變化)事實表相對的。還是以商品維表舉例,緩慢變化維一般就三種解決方法:
  • 1. 重寫維度,也就是覆蓋歷史資料,只保留最新一份
  • 2. 新增維度列,【商品維表】(商品ID,商品名稱)=> 【商品維表】(商品ID,商品歷史名稱,商品最新名稱),這種只合適變化頻率非常非常低的維度屬性(畢竟頻繁變化我們不可能會一直新增列來儲存,特殊情況除外)
  • 3. 插入新的維度行,保留歷史資料,事實表和維度之變化前的維度值關聯
 

快照維

阿里巴巴不建議用緩慢變化維處理維度的變化。
一方面是緩慢變化維需要生成全域性唯一的代理鍵,對於阿里龐大的資料量來說,成本開銷太高;二是加入了代理鍵後會增加ETL的複雜程度。基於這兩點,阿里用的是快照維度,顧名思義就是一天生成一個表切片,每天的全量資料都儲存到對應的歷史分割槽。當我們取任意一天的事實時,可以通過時間對應到每一天的維度歷史中進行關聯即可。但是此方法最大的問題是太浪費儲存!
所以,阿里又推出了極限儲存方案。不過需要注意的是,極限儲存的底層是歷史拉鍊儲存,所以我們下面先講講什麼是歷史拉鍊,以及拉鍊表的優缺點,然後再介紹極限儲存。
 
 

歷史拉鍊儲存

歷史拉鍊儲存是基於處理緩慢變化維的第三種方法來加工的,也就是:新建維度行。
但不同的是,拉鍊儲存還特地用了兩個時間鍵(生效時間和失效時間)來替代原有的代理鍵
 
歷史拉鍊一般用天作為一次記錄變更的最細單位,新資料進來後,舊資料就置為歷史狀態,新資料打上最新標記。本質其實就是為了節省儲存,其次才是為了反映歷史變化。如果一天內變化多次,那就以當天最後一次變更記錄為最新。但如果變化不太頻繁,按天儲存產生太多無用資料,也可以按周、月等做變更單位。
 
歷史拉鍊儲存一般應用場景:針對變化頻率不頻繁的維度表或者是週期性事實表(後面章節會介紹事實表的設計),如修改商品名稱、修改身份證名稱、訂單狀態變更、使用者狀態變更等。
 
舉個例子:商品維度表
2021-01-01日向商品表新增加3個商品,結束時間 9999-12-31表示當前記錄是最新的
商品ID
商品名稱
生效時間
失效時間
1001
阿瑪尼手錶,新到貨!
2021-01-01
9999-12-31
1002
香奈兒脣膏
2021-01-01
9999-12-31
1003
片仔癀
2021-01-01
9999-12-31
 
2021-01-02日,商品編號1002和1002更改了商品名稱,除此之外又新增了1004商品蘋果手機
綠色代表新品,紅色代表修改
商品ID
商品名稱
生效時間
失效時間
1001
阿瑪尼手錶,新到貨!
2021-01-01
2021-01-02
1001
阿瑪尼手錶新名稱
2021-01-02
9999-12-31
1002
香奈兒脣膏
2021-01-01
2021-01-02
1002
香奈兒脣膏新名稱
2021-01-02
9999-12-31
1003
片仔癀
2021-01-01
9999-12-31
1004
蘋果手機
2021-01-02
9999-12-31
 
這樣一來,當我們需要查詢商品最新記錄時,只需要卡一下 結束時間='9999-12-31'
select *
from 商品維表
where 結束時間='9999-12-31'
同樣,如果我們想查商品在2021-01-01的所有商品狀態
select *
from 商品維表
where 開始時間 <='2021-01-01' and 結束時間 >= '2021-01-01'
查出來的結果就是2021-01-01日的三條商品記錄。
 
所以最後會發現,如果每條資料每日都變化,天拉鍊會沒有意義。反之,每天所有商品資料都不發生變化,此時,只需要儲存一天的資料即可,也不需要拉鍊
 

極限儲存

其實阿里的極限儲存底層依然還是歷史拉鍊表,但是它按月做拉鍊,另外還封裝了一層hook,當使用者查詢歷史時,先從最近的歷史快照中獲取對映,再去歷史拉鍊裡面查。感覺就是歷史拉鍊儲存做了些許延伸,不細說。
 
 

特殊維度

 

遞迴層次

維度的遞迴層次,按照層級是否固定分為均衡層次結構和非均衡層次結構。比如類目,有固定數量的級別,分別是葉子類目、五級類 目、四級類目、三級類目、二級類目、 級類曰:地區,分別是鄉鎮/街道區縣、城市、省份、國家。對於這種具有固定數量級別的遞迴層次,稱為“均衡層次結構”。反之,對於數量級別不固定的遞迴層次,稱為“非均衡層次結構”。
類目ID
類目名稱
父類目ID
是否葉子結點
1001
A
N
1002
B
1001
N
1003
C
1001
N
1004
D
1003
Y
1005
E
1002
Y
1006
F
1002
Y
1007
G
1006
Y
其關係如下圖:
對於遞迴層級維度,當樹的高度不深的時候,我們只需要把它扁平化處理。
如生成下面的類目維度表:
【類目維度表】(類目ID,類目名稱,類目層級,一級類目ID,一級類目名稱,二級類目ID,二級類目名稱,三級類目ID,三級類目名稱... ... ,是否葉子類目)
其中如果只有高層級類目,沒有低層級類目,就將低層級類目置空。
有些資料庫或者數倉查詢引擎是不支援遞迴查詢的,遞迴的類目層級需要用語言特殊處理。
 
 

行為維度

通過事實表發生的事實計算出來的維度,稱為行為維度或事實衍生維度。如按照交易金額劃分的等級維度、賣家的信用度等。
按照加工方式,行為維度可以劃分為以下幾種:
  • 另一個維度的過去行為,如買家最近一次訪問淘寶的時間、 買家最近一次發生淘寶交易的時間等。
  • 快照事實行為維度,如買家從年初截至當前的淘寶交易金額、買家信用分值 、賣家信用分值等。
  • 分組事實行為維度 ,將數值型事實轉換為列舉值。如買家從年初截至當前的淘寶交易金額按照金額劃分的等級 買家信用分值按照分數劃分得到的信用等級等。
  • 複雜邏輯事實行為維度,通過複雜演算法加工或多個事實綜合加工得到。如賣家主營類目,商品熱度根據訪問、收藏、加人購物車、交易等情況綜合計算得到。
對於行為維度,一般有兩種處理方式,一是新建維表;二是冗餘到現有的維度表中。
 
 

多值維度

如果一個事實表的某一條記錄在維度表中找到多條記錄對應,就成為多值維度。
如:電商系統一般會把訂單分為大訂單和小訂單,一個大訂單可以購買2個以上商品,小訂單每單是1個商品。
如果從大訂單事實表去關聯商品維表,那明顯是又問題的。
針對多值維度,可以通過以下兩種思路解決:
  • 降低事實表的粒度。本例中可以把大訂單分攤為對應的小訂單,再關聯商品維表
  • 在事實表中增加對應的維度ID欄位(類似增加一個角色ID)。如:房屋共買,參考

 

雜項維度

源業務系統中往往會有特別多的列舉值、指示符或者低基數標誌欄位、開關欄位等。如:訂單狀態、微信場景、廣告渠道、是否XXX(01標記)等。一般來說,如果這種維度少的話,可以在事實表中儲存,但是如果特別多,建議剝離出事實表,單獨儲存到一張雜項維度表中,生成對應的代理鍵後,事實表只需儲存維度的代理鍵即可。
 
例如,在銷售訂單中,可能存在有很多離散資料(yes-no這種開關型別的值),如:
  • verification_ind(是否被稽核,如果訂單已經被稽核,值為yes)
  • credit_check_flag(是否信用狀態被檢查,表示此訂單的客戶信用狀態是否已經被檢查)
  • new_customer_ind(是否新客首單,如果這是新客戶的首個訂單,值為yes)
  • web_order_flag(是否線上,表示一個訂單是線上上訂單還是線下訂單)
我們需要新增一個名為sales_order_attribute_dim的雜項維度表,該表包括四個yes-no列:verification_ind、credit_check_flag、new_customer_ind和web_order_flag。每個列可以有兩個可能值中的一個,Y 或 N,因此sales_order_attribute_dim表最多有16(2^4=16)行。
注意:
假設這16行已經包含了所有可能的組合,並且不考慮雜項維度修改的情況,則可以直接通過笛卡爾積生成記錄後插入到雜項維度表中。但如果基數大,生成的笛卡爾積數量爆炸式增長,則不建議直接生成所有組合的完整的雜項維度表,我們只需要在抽取的過程中遇到新的組合就生成相應記錄即可。
 
具體表結構如下圖所示:
 
注意:上述的這種維表只歸屬於訂單,針對公共性強(多個事實表都可用)的雜項維度,還是可以考慮單獨給他建一個統一的維表存放的。
 

總結

本篇簡單闡述了維度的概念,型別,設計維度的方法,模型等內容。可以說所有的內容都是奔著一個議題去的:
如何把維度設計規範化、簡單易用、統一使用?如何儘可能地節省儲存維度的空間?
維度建模中的維度表設計是在確定業務需求和資料粒度後開展的,而維度設計之後我們們將在下一篇繼續討論事實表的設計。
 

相關文章