開篇介紹
關於 Slowly Changing Dimension 緩慢漸變維度的理論概念請參看 數據倉庫系列 - 緩慢漸變維度 (Slowly Changing Dimension) 常見的三種類型及原型設計
本篇文章總結了實現緩慢漸變維度的幾種方式,並且分析了 Changing Attribute 和 Historical Attribute 輸出的邏輯過程。
- 示例一:SSIS 中使用 Slowly Changing Dimension 控件
- 示例二:使用 SQL 中 Merge 語句實現簡單的 SCD 效果
- 示例三:在 SSIS 中使用 Lookup, Conditional Split, Multicast 等控件實現 SCD 效果
測試表以及測試數據
其中 Customer 是數據源表,DimCustomer 模擬的是數據倉庫中的 Customer 維度表。
每個示例都是從空表開始,第一次運行的時候 Dimension 表沒有數據,第二次運行之前將添加幾條數據到 Customer 數據源表中,並同時修改若干數據。
但是要注意這個示例對數據源數據的加載是全部加載,而不考慮基於數據源數據的增量加載,關於增量加載的實現會放在 BI 系列的其它文章中講解。
USE BIWORK_SSIS GO IF OBJECT_ID('Customer') IS NOT NULL DROP TABLE Customer GO IF OBJECT_ID('DimCustomer') IS NOT NULL DROP TABLE DimCustomer GO CREATE TABLE Customer ( ID INT PRIMARY KEY IDENTITY(1,1), FullName NVARCHAR(50), City NVARCHAR(50), Occupation NVARCHAR(50) ) CREATE TABLE DimCustomer ( CustomerID INT PRIMARY KEY IDENTITY(1,1), CustomerAlternateKey INT, FullName NVARCHAR(50), City NVARCHAR(50), Occupation NVARCHAR(50), StartDate DATETIME, EndDate DATETIME, IsCurrent BIT DEFAULT(1) ) INSERT INTO BIWORK_SSIS.dbo.Customer VALUES ('BIWORK','Beijing','IT'), ('ZhangSan','Shanghai','Education'), ('Lisi','Guangzhou','Student')
示例一 SSIS 中的 Slowly Changing Dimension
新建一個 Package 並拖放一個 Data Flow,在 Data Flow 中建立好與 Customer 表的數據源連接,新建 Slowly Changing Dimension SCD_DimCustomer。

雙擊 SCD_DimCustomer 編輯相關的屬性。
Input Columns 來源於上游數據源即 Customer 表,Dimension Columns 描述 DimCustomer 表信息。
Key Type - Business Key 表示 Customer.ID 與 DimCustomer.CustomerAlternateKey 關聯,后面的數據更新或者插入就跟這個 Business Key 相關。
其主要邏輯是以 Customer.ID 對比 DimCustomer.CustomerAlternateKey ,如果關聯不到則表示 Customer 中有新數據則將新數據插入到 DimCustomer 中。
如果關聯到則檢查哪些字段是不需要更新 SCD Type 0,哪些字段的數據是需要更新的 SCD Type 1。

下一步設計 DimCustomer 表中幾個屬性字段。
City - 歷史數據,如果 City 發生更改則添加一條新的數據而保留此歷史信息 - Type 2。
FullName - 固定的值,此字段的數據在數據倉庫中不發生更改 - Type 0。
Occupation - 可更改的值,如果 Occupation 發生更改則只修改它而不保留歷史信息 - Type 1。

在這里暫時不設置 - 如果檢測到 Customer 中 FullName 發生更改就報錯。

對於 Type 2 Historical Attribute 的設計是使用有效時間段來表示的,具體的理論概念請參看 數據倉庫系列 - 緩慢漸變維度 (Slowly Changing Dimension) 常見的三種類型及原型設計 其中有詳細的講解。第一個選擇是使用標志字段來表示這個記錄是否到期或者是當前使用的,在我們現在的這個例子中可以先設計為有效期,后面可以修改讓兩種方式都存在。

推斷成員的設置,暫時這里不設置推斷成員。推斷成員一般發生在維度表的數據載入落后於Fact事實表的數據載入,因此Fact事實表數據加載在前因此就引用不到相應的Dimension Key而造成這個問題,這個以后會專門寫一篇文章來討論推斷成員。

Slowly Changing Dimension 這個控件此時會產生兩個分支邏輯三組輸出。

設置完了之后會自動生成其它的所有邏輯,並且已經幫助實現了 SCD 的功能。

執行之后看看具體的效果 -

分析一下 Slowly Changing Dimension 的邏輯。
其中 New Output 輸出就是直接插入新的紀錄到 DimCustomer 中。
Historical Attribute Insert Output 向下的 OLE DB Command 中 SQL 語句為 -
UPDATE [dbo].[DimCustomer] SET [EndDate] = ? WHERE [CustomerAlternateKey] = ? AND [EndDate] IS NULL
對於歷史的數據應該是修改 EndDate 將這條數據表示終止狀態,並且繼續添加一條新的數據。在這里因為多添加了一個 IsCurrent 來表示記錄的狀態,因此這條 SQL 語句應該修改為:IsCurrent = 0,這個邏輯需要在 SSIS 中做出細微的調整。
UPDATE [dbo].[DimCustomer] SET [EndDate] = ?, [IsCurrent] = ? WHERE [CustomerAlternateKey] = ? AND [EndDate] IS NULL
Changing Attribute Update Output 向下的 OLE DB Command 1 中 SQL 語句為 -
UPDATE [dbo].[DimCustomer] SET [Occupation] = ? WHERE [CustomerAlternateKey] = ? AND [EndDate] IS NULL
對於 SCD Type 1 的屬性只需要直接更改即可,因此直接根據 Customer.ID 即關聯到的 DimCustomer.CustomerAlternateKey 修改相應的屬性。
對於 Historical Attribute Insert Output 下的 Derived Column 和 OLE DB Command 中作出的修改:
Derived Column 新增加一個 HistoricalCurrent ,其值為0,用來表示當條記錄為歷史記錄。

修改 SQL 語句

修改 Column Mapping
對源數據做出一定的修改:
-- 新插入一條 INSERT INTO BIWORK_SSIS.dbo.Customer VALUES ('Wangwu','Beijing','Finance') -- 修改 Changing Attribute UPDATE BIWORK_SSIS.dbo.Customer SET Occupation = 'IT' WHERE ID = 3 -- 同時修改 Changing Attribute 和 Historical Attribute UPDATE BIWORK_SSIS.dbo.Customer SET Occupation = 'Publisher', City = 'Hangzhou' WHERE ID = 2
再次執行 SSIS Package 並查詢數據庫結果 -

新增的一條數據是 Wangwu ,因此將直接添加新的一條記錄到 DimCustomer 中。
ZhangSan 因為修改了 City ,因此屬於 Type 2 SCD 需要保留歷史數據。所以先修改 ZhangSan 的 EndDate 和 IsCurrent 保留這條歷史數據,然后再將最新的數據添加到 DimCustomer 中,也就是最后看到的 ZhangSan - Hangzhou - Publisher
Lisi 因為修改了 Occupation 屬於 Type 1 SCD 只需要修改原數據即可,所以 Lisi 的 Occupation 直接更新為 IT 即可。
邏輯圖解
下面是對 SCD Type 1 和 Type 2 實現邏輯的總結,如果理解了這些邏輯我們也完全可以用其它的 SSIS 控件來實現 SCD 的功能。

Type 2 SCD 要比 Type 1 要復雜一些,它有一個 Update 之后的 Insert 操作。

示例二 - 使用 SQL 中 MERGE 語句實現 SCD Type 1 和 SCD Type 2 的功能
SQL MERGE 語句非常實用,可以非常簡單的根據一些關聯條件來比較兩個表的數據,然后決定匹配的邏輯如何執行和不匹配的時候邏輯如何處理。關於 SQL MERGE 的語法和使用請參照 SQL Server - 使用 Merge 語句實現表數據之間的對比同步
使用 MERGE 語句來實現上面的效果
-- Type 2 SCD MERGE INTO dbo.DimCustomer AS Dim USING dbo.Customer AS Src ON Dim.CustomerAlternateKey = Src.ID WHEN NOT MATCHED BY TARGET THEN INSERT VALUES(Src.ID,Src.FullName,Src.City,Src.Occupation,GETDATE(),NULL,1) WHEN MATCHED AND Dim.City <> Src.City THEN UPDATE SET Dim.EndDate = GETDATE(),Dim.IsCurrent = 0 ; -- Type 1 SCD MERGE INTO dbo.DimCustomer AS Dim USING dbo.Customer AS Src ON Dim.CustomerAlternateKey = Src.ID AND Dim.IsCurrent = 1 WHEN NOT MATCHED BY TARGET THEN INSERT VALUES(Src.ID,Src.FullName,Src.City,Src.Occupation,GETDATE(),NULL,1) WHEN MATCHED AND Dim.Occupation <> Src.Occupation THEN UPDATE SET Dim.Occupation = Src.Occupation ;
因為在 MERGE 語句中有一些語法限制
- 在 Merge Matched 操作中,只能允許執行 UPDATE 或者 DELETE 語句。
- 在 Merge Not Matched 操作中,只允許執行 INSERT 語句。
- 一個 Merge 語句中出現的 Matched 操作,只能出現一次 UPDATE 或者 DELETE 語句,否則就會出現下面的錯誤 - An action of type 'WHEN MATCHED' cannot appear more than once in a 'UPDATE' clause of a MERGE statement.
- Merge 語句最后必須包含分號,以 ; 結束。
所以在這里采取的方式是:
Type 2 SCD 注釋的地方 - 根據 Customer.ID = DimCustomer.CustomerAlternateKey 關聯如果沒有找到匹配的記錄,就意味是新數據,直接插入到 DimCustomer 表中。
如果匹配到了即此數據在維度表中也存在,因此先將此記錄更新完畢標志此條記錄為歷史記錄 - EndDate 和 IsCurrent 都設置了值表示 SCD Type 2。
Type 1 SCD 注釋的地方 - 因為剛才的歷史記錄已經被標識為 IsCurrent = 0, 因此在此時的邏輯將匹配不到數據,因此作為新數據插入,這樣就延續了 SCD Type 2 Update 之后的 Insert 操作。
對於匹配到的數據,再來比較 SCD Type 1 的列,如果不匹配的話那么就直接更新掉就可以了。
和示例一使用相同的測試數據和相同的數據修改方式后,執行完的效果也是一樣的。
第一次執行

修改完測試數據之后再次執行

在 SSIS 中使用 Lookup, Conditional Split, Multicast 等控件實現 SCD 效果
一旦理解了 SCD 的實現邏輯,我們完全可以自己通過 SSIS 中的其它 Task 來實現 Slowly Changing Dimension。
會使用到的 Task 包括 Lookup,Multicast,Conditional Split 等。
可以參看相應的 Task 的Demo 和一些原理介紹:
- 微軟BI 之SSIS 系列 - Lookup 組件的使用與它的幾種緩存模式 - Full Cache, Partial Cache, NO Cache
- 微軟BI 之SSIS 系列 - 在 SSIS 中使用 Multicast Task 將數據源數據同時寫入多個目標表,備份數據表,以及寫入Audit 信息
新建一個 Data Flow Task 並且仍然將 Customer 表作為數據源,拖放一個 Lookup Task 並完成以下配置。

LKP_DimCustomer 中 Reference Table 引用集/引用表是 DimCustomer。

左邊是Customer表,右邊是要去 Look Up 的 DimCustomer,Customer.ID = DimCustomer.CustomerAlternateKey 關聯。

基於 Customer.ID = DimCustomer.CustomerAlternateKey 就會有兩種結果,匹配的輸出和不匹配的輸出。
不匹配的輸出就是添加新數據。
匹配的輸出就是要去檢查 Historical Attribute "City" 有沒有更改,如果有更改就是一次 Update 然后加上一次 Insert 操作。
如果 Changing Attribute "Occupation" 有更改就是一次 Update 操作。
中間會使用到的三個狀態 - StartDate , EndDate, IsCurrent 都會在整個流程中使用到,主要用來更新它們的狀態。

先實現不匹配的邏輯,即先添加一條新的數據。

DC_NewInsertStartDate 需要准備 StartDate 和 IsCurrent = 1

OLE_DST_DimCustomer 的配置

Customer.ID = DimCustomer.CustomerAlternateKey 匹配的情況下有兩種情況:
City 不匹配 和 Occupation 不匹配,添加一個 Conditional Split 並連接到 Lookup 的匹配輸出上。

下面是全部的實現效果 - Changing Update 下的邏輯是直接修改 DimCustomer 的數據,OLE_CMD_Update 中
UPDATE [dbo].[DimCustomer] SET [Occupation] = ? WHERE [CustomerAlternateKey] = ? AND [EndDate] IS NULL

Historical_Update 下使用了一個 Multicast 將數據流分為兩個分支,因為它是 Historical Attribute Update,因此邏輯是更新原歷史數據,添加新數據。
OLE_CMD_UpdateHistorical 中的 SQL 語句,這里的 IsCurrent 將最終更新為 0 。
UPDATE [dbo].[DimCustomer] SET [EndDate] = ? ,IsCurrent = ? WHERE [CustomerAlternateKey] = ? AND [EndDate] IS NULL
使用前兩個示例中的測試數據,第一次執行完 SSIS Package 之后三條數據走向了 Lookup No Match Output 表示新數據。

查詢數據表結果

修改完測試數據之后再次執行,數據源 1 條是新數據走向 Lookup No Match Output,1 條是 Historical Update 因此需要 Update 歷史數據然后再添加一條新數據,1 條是 Changing Update 因此直接 Update 就可以了。

執行效果如下所示

那么至此,這三種對於 Slowly Chaning Dimension 緩慢漸變維度的實現就全部演示完了。從中可以發現,整個 SCD 處理的邏輯在三個示例中本質上都是一樣的。都是圍繞着 Business Key 匹配和不匹配的結果來展開的,並且在這個過程中區別了 SCD Type 1 和 Type 2。對於 Type 1 就是一個更新操作,對於 Type 2 不僅有更新操作而且還有插入操作。只要理解了它們的實現邏輯,使用不同的方式實現起來並不困難。
這三種方式中,第一種方式即直接使用 SSIS 中提供的 SCD Task 實現起來最為簡單,基本上都是配置性的內容。但是往往因為數據量過大可能造成性能上的問題,因此才會有示例二和示例三中出現的方法。第二種方式代碼更為直接,但是如果遇到多個屬性變化,在代碼上會有一些變化,這個需要仔細認真的檢查和測試。第三種方式相對於第一種方式要花費更多的時間,但是在實現方式上可以更為靈活的滿足各種需要。
我並沒有基於這三個示例做出性能上的測試,因為在實際的維度變化設計中,Historical Attribute 和 Changing Attribute 可能不止一個,可能會有多個。並且維度表的大小,數據源表的大小對性能上的影響也都存在。所以在這里只是提出常用到可以解決 SCD 問題的幾種方式,並且可以根據實際的需求進行緩慢維度變化設計,根據實際測試的效率高低來選擇合適的方案。
寫的比較多,總結如果有不足或者遺漏之處還望指出,謝謝! 另外:關於 Inferred Member 會另外專門寫隨筆總結!
PS:后面兩個案例實際上是有錯誤的,大家如果仔細看的話。在檢查歷史數據的情況下,Lookup Dim 包括 Merge 里面的查找對比是應該要加上一個條件的 IsCurrent = 1,因為在比較 CustomerAlternateKey 的時候是只能與當前記錄對比的,而不應該包含歷史記錄部分的對比。Multicast 可以去掉,將插入挪到更新之后,這樣保證更新歷史在前,插入新紀錄在后。
上面的圖片和文字我就不一一修改了,保留這些錯誤讓大家也能看到這些錯誤的原因和解決過程,請大家自行測試發現錯誤糾正。
更多 BI 文章請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server) 如果覺得這篇文章看了對您有幫助,請幫助推薦,以方便他人在 BIWORK 博客推薦欄中快速看到這些文章。
