開源ETL工具kettle系列

開源ETL工具kettle系列之常見問題

摘要:本文主要介紹使用kettle設計一些ETL任務時一些常見問題,這些問題大部分都不在官方FAQ上,你可以在kettle的論壇上找到一些問題的答案

1. Join
我得到A 數據流(不管是基於文件或數據庫),A包含field1 , field2 , field3 字段,然後我還有一個B數據流,B包含field4 , field5 , field6 , 我現在想把它們 ‘加’ 起來, 應該怎麼樣做.
這是新手最容易犯錯的一個地方,A數據流跟B數據流能夠Join,肯定是它們包含join key ,join key 可以是一個字段也可以是多個字段。如果兩個數據流沒有join key ,那麼它們就是在做笛卡爾積,一般很少會這樣。比如你現在需要列出一個員工的姓名和他所在部門的姓名,如果這是在同一個數據庫,大家都知道會在一個sql 裏面加上where 限定條件,但是如果員工表和部門表在兩個不同的數據流裏面,尤其是數據源的來源是多個數據庫的情況,我們一般是要使用Database Join 操作,然後用兩個database table input 來表示輸入流,一個輸入是部門表的姓名,另一個是員工表的姓名,然後我們認爲這兩個表就可以 ”Join” 了,我們需要的輸出的確是這兩個字段,但是這兩個字段的輸出並不代表只需要這兩個字段的輸入,它們之間肯定是需要一個約束關係存在的。另外,無論是在做 Join , Merge , Update , Delete 這些常規操作的時候,都是先需要做一個compare 操作的,這個compare 操作都是針對compare key 的,無論兩個表結構是不是一樣的,比如employee 表和department 表,它們比較的依據就是employee 的外鍵department_id , 沒有這個compare key 這兩個表是不可能連接的起來的.. 對於兩個表可能還有人知道是直接sql 來做連接,如果是多個輸入數據源,然後是三個表,有人就開始迷茫了,A表一個字段,B表一個字段,C表一個字段,然後就連Join操作都沒有,直接 database table output , 然後開始報錯,報完錯就到處找高手問,他們的數據庫原理老師已經在吐血了。如果是三個表連接,一個sql 不能搞定,就需要先兩個表兩個表的連接,通過兩次compare key 連接之後得到你的輸出,記住,你的輸出並不能代表你的輸入. 下面總結一下:
1. 單數據源輸入,直接用sql 做連接
2. 多數據源輸入,(可能是文本或是兩個以上源數據庫),用database join 操作.
3. 三個表以上的多字段輸出.

2. Kettle的數據庫連接模式
Kettle的數據庫連接是一個步驟裏面控制一個單數據庫連接,所以kettle的連接有數據庫連接池,你可以在指定的數據庫連接裏面指定一開始連接池裏面放多少個數據庫連接,在創建數據庫連接的時候就有Pooling 選項卡,裏面可以指定最大連接數和初始連接數,這可以一定程度上提高速度.

3. transaction
我想在步驟A執行一個操作(更新或者插入),然後在經過若干個步驟之後,如果我發現某一個條件成立,我就提交所有的操作,如果失敗,我就回滾,kettle提供這種事務性的操作嗎?
Kettle 裏面是沒有所謂事務的概念的,每個步驟都是自己管理自己的連接的,在這個步驟開始的時候打開數據庫連接,在結束的時候關閉數據庫連接,一個步驟是肯定不會跨session的(數據庫裏面的session), 另外,由於kettle是並行執行的,所以不可能把一個數據庫連接打開很長時間不放,這樣可能會造成鎖出現,雖然不一定是死鎖,但是對性能還是影響太大了。ETL中的事務對性能影響也很大,所以不應該設計一種依賴與事務方式的ETL執行順序,畢竟這不是OLTP,因爲你可能一次需要提交的數據量是幾百 GB都有可能,任何一種數據庫維持一個幾百GB的回滾段性能都是會不大幅下降的.

4. 我真的需要transaction 但又不想要一個很複雜的設計,能不能提供一個簡單一點的方式
Kettle 在3.0.2GA版中將推出一種新功能,在一個table output 步驟中有一個Miscellaneous 選項卡,其中有一個Use unique connections 的選項,如果你選中的話就可以得到一個transaction 的簡單版,
由於是使用的單數據庫連接,所以可以有錯誤的時候回滾事務,不過要提醒一點是這種方式是以犧牲非常大的性能爲前提條件的,對於太大的數據量是不適合的(個人仍然不建議使用這種方式)

5. temporary 表如何使用
我要在ETL過程中創建一箇中間表,當某個條件成立的時候,我要把中間表的數據進行轉換,當另一條件成立的時候我要對中間表進行另一個操作,我想使用數據庫的臨時表來操作,應該用什麼步驟。
首先從temp 表的生命週期來分,temp分爲事務臨時表和會話臨時表,前面已經解釋過了,kettle是沒有所謂事務的概念的,所以自然也沒有所謂的事務臨時表。Kettle的每個步驟管理自己的數據庫連接,連接一結束,kettle也就自然丟掉了這個連接的session 的handler , 沒有辦法可以在其他步驟拿回這個session 的handler , 所以也就不能使用所謂的會話臨時表,當你嘗試再開一個連接的時候,你可以連上這個臨時表,但是你想要的臨時表裏面的數據都已經是空的(數據不一定被清除了,但是你連不上了),所以不要設計一個需要使用臨時表的轉換
之所以會使用臨時表,其實跟需要 ”事務” 特性有一點類似,都是希望在ETL過程中提供一種緩衝。臨時表很多時候都不是某一個源表的全部數據的鏡像,很多時候臨時表都是很小一部分結果集,可能經過了某種計算過程,你需要臨時表無非是基於下面三個特性:
1. 表結構固定,用一個固定的表來接受一部分數據。
2. 每次連接的時候裏面沒有數據。你希望它接受數據,但是不保存,每次都好像執行了truncate table 操作一樣
3. 不同的時候連接臨時表用同一個名字,你不想使用多個連接的時候用類似與temp1 , temp2 , temp3 , temp4 這種名字,應爲它們表結構一樣。
既然臨時表不能用,應該如何設計ETL過程呢?(可以用某種詭異的操作搞出臨時表,不過不建議這樣做罷了)
如果你的ETL過程比較的單線程性,也就是你清楚的知道同一時間只有一個這樣的表需要,你可以創建一個普通的表,每次連接的時候都執行truncate 操作,不論是通過table output 的truncate table 選項,還是通過手工執行truncate table sql 語句(在execute sql script 步驟)都可以達到目的(基於上面的1,2 特性)
如果你的ETL操作比較的多線程性,同一時間可能需要多個表結構一樣並且裏面都是爲空的表(基於上面1,2,3特性),你可以創建一個 “字符串+序列”  的模式,每次需要的時候,就創建這樣的表,用完之後就刪除,因爲你自己不一定知道你需要多少個這種類型的表,所以刪除會比truncate 好一些。
下面舉個例子怎麼創建這種表:
你可以使用某種約定的表名比如department_temp 作爲department 的臨時表。或者
把argument 傳到表名,使用 department_${argument} 的語法,
如果你需要多個這種表,使用一個sequence 操作+execute sql script 操作,execute sql script 就下面這種模式
          Create table_?  (…………..)
在表的名字上加參數,前面接受一個sequence 或類似的輸入操作.
需要注意的是這種參數表名包括database table input 或者execute sql script ,只要是參數作爲表名的情況前面的輸入不能是從數據庫來的,應爲沒有辦法執行這種preparedStatement  語句,從數據庫來的值後面的操作是 “值操作” ,而不是字符串替換,只有argument 或者sequence 操作當作參數纔是字符串替換. (這一點官方FAQ也有提到)

6. update table 和execute sql script 裏面執行update 的區別
執行update table 操作是比較慢的,它會一條一條基於compare key 對比數據,然後決定是不是要執行update sql , 如果你知道你要怎麼更新數據儘可能的使用execute sql script 操作,在裏面手寫update sql (注意源數據庫和目標數據庫在哪),這種多行執行方式(update sql)肯定比單行執行方式(update table 操作)快的多。
另一個區別是execute sql script 操作是可以接受參數的輸入的。它前面可以是一個跟它完全不關的表一個sql :
select field1, field2 field3 from tableA
後面執行另一個表的更新操作:
update tableB set field4 = ? where field5=? And field6=?
        然後選中execute sql script 的execute for each row .注意參數是一一對應的.(field4 對應field1 的值,
field5 對應field2 的值, field6 對應field3 的值)

7. kettle的性能
kettle本身的性能絕對是能夠應對大型應用的,一般的基於平均行長150的一條記錄,假設源數據庫,目標數據庫以及kettle都分別在幾臺機器上(最常見的桌面工作模式,雙核,1G內存),速度大概都可以到5000 行每秒左右,如果把硬件提高一些,性能還可以提升 , 但是ETL 過程中難免遇到性能問題,下面一些通用的步驟也許能給你一些幫助.
儘量使用數據庫連接池
儘量提高批處理的commit size
儘量使用緩存,緩存儘量大一些(主要是文本文件和數據流)
Kettle 是Java 做的,儘量用大一點的內存參數啓動Kettle.
可以使用sql 來做的一些操作儘量用sql
Group , merge , stream lookup ,split field 這些操作都是比較慢的,想辦法避免他們.,能用sql 就用sql
插入大量數據的時候儘量把索引刪掉
儘量避免使用update , delete 操作,尤其是update , 如果可以把update 變成先delete ,  後insert .
能使用truncate table 的時候,就不要使用delete all row 這種類似sql
合理的分區
如果刪除操作是基於某一個分區的,就不要使用delete row 這種方式(不管是delete sql 還是delete 步驟),直接把分區drop 掉,再重新創建
儘量縮小輸入的數據集的大小(增量更新也是爲了這個目的)
儘量使用數據庫原生的方式裝載文本文件(Oracle 的sqlloader , mysql 的bulk loader 步驟)
儘量不要用kettle 的calculate 計算步驟,能用數據庫本身的sql 就用sql ,不能用sql 就儘量想辦法用procedure , 實在不行纔是calculate 步驟.
要知道你的性能瓶頸在哪,可能有時候你使用了不恰當的方式,導致整個操作都變慢,觀察kettle log 生成的方式來了解你的ETL操作最慢的地方。
遠程數據庫用文件+FTP 的方式來傳數據 ,文件要壓縮。(只要不是局域網都可以認爲是遠程連接)

8. 描述物理環境
源數據庫的操作系統,硬件環境,是單數據源還是多數據源,數據庫怎麼分佈的,做ETL的那臺機器放在哪,操作系統和硬件環境是什麼,目標數據倉庫的數據庫是什麼,操作系統,硬件環境,數據庫的字符集怎麼選,數據傳輸方式是什麼,開發環境,測試環境和實際的生產環境有什麼區別,是不是需要一箇中間數據庫(staging 數據庫) ,源數據庫的數據庫版本號是多少,測試數據庫的版本號是多少,真正的目標數據庫的版本號是多少……. 這些信息也許很零散,但是都需要一份專門的文檔來描述這些信息,無論是你遇到問題需要別人幫助的時候描述問題本身,還是發現測試環境跟目標數據庫的版本號不一致,這份專門的文檔都能提供一些基本的信息

9. procedure
爲什麼我不能觸發procedure?
這個問題在官方FAQ裏面也有提到,觸發procedure 和 http client 都需要一個類似與觸發器的條件,你可以使用generate row 步驟產生一個空的row ,然後把這條記錄連上procedure 步驟,這樣就會使這條沒有記錄的空行觸發這個procedure (如果你打算使用無條件的單次觸發) ,當然procedure 也可以象table input 裏面的步驟那樣傳參數並且多次執行.
另外一個建議是不要使用複雜的procedure 來完成本該ETL任務完成的任務,比如創建表,填充數據,創建物化視圖等等.

10. 字符集
Kettle使用Java 通常使用的UTF8 來傳輸字符集,所以無論你使用何種數據庫,任何數據庫種類的字符集,kettle 都是支持的,如果你遇到了字符集問題,也許下面這些提示可以幫助你:
1. 單數據庫到單數據庫是絕對不會出現亂碼問題的,不管原數據庫和目標數據庫是何種種類,何種字符集
2. 多種不同字符集的原數據庫到一個目標數據庫,你首先需要確定多種源數據庫的字符集的最大兼容字符集是什麼,如果你不清楚,最好的辦法就是使用UTF8來創建數據庫.
3. 不要以你工作的環境來判斷字符集:現在某一個測試人員手上有一個oracle 的基於xxx 字符集的已經存在的數據庫,並且非常不幸的是xxx 字符集不是utf8 類型的,於是他把另一個基於yyy字符集的oracle 數據庫要經過某一個ETL過程轉換到oracle , 後來他發現無論怎麼樣設置都會出現亂碼,這是因爲你的數據庫本身的字符集不支持,無論你怎麼設置都是沒用的. 測試的數據庫不代表最後產品運行的數據庫,尤其是有時候爲了省事把多個不同的項目的不相關的數據庫裝在同一臺機器上,測試的時候又沒有分析清楚這種環境,所以也再次強調描述物理環境的重要性.
4. 你所看到的不一定代表實際儲存的:mysql 處理字符集的時候是要在jdbc 連接的參數裏面加上字符集參數的,而oracle 則是需要服務器端和客戶端使用同一種字符集才能正確顯示,所以你要明確你所看到的字符集亂碼不一定代表真的就是字符集亂碼,這需要你檢查在轉換之前的字符集是否會出現亂碼和轉換之後是否出現亂碼,你的桌面環境可能需要變動一些參數來適應這種變動
5. 不要在一個轉換中使用多個字符集做爲數據源.

11. 預定義時間維
Kettle提供了一個小工具幫助我們預填充時間維,這個工具在kettle_home / samples / transformations / General – populate date dimension. 這個示例產生的數據不一定能滿足各種需要,不過你可以通過修改這個示例來滿足自己的需求.

12. SQL tab 和 Options tab
在你創建一個數據庫連接的時候除了可以指定你一次需要初始化的連接池參數之外(在 Pooling 選項卡下面),還包括一個Options 選項卡和一個 SQL 選項卡, Options 選項卡里面主要設置一些連接時的參數,比如autocommit 是on 還是off , defaultFetchSize , useCursorFetch (mysql 默認支持的),oracle 還支持比如defaultExecuteBatch , oracle.jdbc.StreamBufferSize, oracle.jdbc.FreeMemoryOnEnterImplicitCache ,你可以查閱對應數據庫所支持的連接參數,另外一個小提示:在創建數據庫連接的時候,選擇你的數據庫類型,然後選到Options 選項卡,下面有一個Show help text on options usage , 點擊這個按鈕會把你帶到對應各個數據庫的連接參數的官方的一個參數列表頁面,通過查詢這個列表頁面你就可以知道那種數據庫可以使用何種參數了.
對於SQL 選項卡就是在你一連接這個Connection 之後,Kettle 會立刻執行的sql 語句,個人比較推薦的一個sql 是執行把所有日期格式統一成同一格式的sql ,比如在oracle 裏面就是:
   alter session set nls_date_format = xxxxxxxxxxxxx
   alter session set nls_xxxxxxxxx = xxxxxxxxxxxx
這樣可以避免你在轉換的時候大量使用to_date() , to_char 函數而僅僅只是爲了統一日期格式,對於增量更新的時候尤其適用.

13. 數據複製
有的時候可能我們需要的是類似數據複製或者一個備份數據庫,這個時候你需要的是一種數據庫私有的解決方案,Kettle 也許並不是你的第一選擇,比如對於Oracle 來說,可能rman , oracle stream , oracle replication 等等, mysql 也有mysql rmaster / slave 模式的replication 等私有的解決方法,如果你確定你的需求不是數據集成這方面的,那麼也許kettle 並不是一個很好的首選方案,你應該諮詢一下專業的DBA人士也會會更好.

14. 如何控制版本變更
Kettle 的每一個transformation 和job 都有一個version 字段(在你保存的時候), 不過這個功能還不實用,如果你需要版本控制的話,還是建議你將transformation 和job 轉換成文本文件保存,然後用svn 或cvs 或任意你熟悉的版本控制系統將其保存,kettle 將在下一個版本加入版本控制的功能(做的更易用).

15. 支持的數據源
Kettle 支持相當廣的數據源,比如在數據庫裏面的一些不太常見的Access , MaxDB (SAP DB) , Hypersonic , SAP R/3 system , Borland Interbase , Oracle RDB , Teradata和3.0新加入的Sybase IQ .
另外還包括Excel , CSV , LDAP ,以及OLAP Server Mondrian , 目前支持Web Service 不過暫時還不支持SOAP.

16. 調試和測試
當ETL轉換出現不可預知的問題時,或是你不清楚某個步驟的功能是什麼的情況下,你可能需要創建一個模擬環境來調適程序,下面一些建議可能會有所幫助:
儘量使用generate row 步驟或者固定的一個文本文件來創建一個模擬的數據源
模擬的數據源一定要有代表性,數據集一定儘量小(爲了性能考慮)但是數據本身要足夠分散.
創建了模擬的數據集後你應該清楚的知道你所要轉換之後的數據時什麼樣的.

17. 錯誤處理
在ETL任務中由於數據問題出現轉換錯誤是一件非常正常的事情,你不應該設計一個依賴於臨時表或者擁有事務特點的ETL過程,面對數據源質量問題的巨大挑戰,錯誤處理是並不可少的,kettle同樣提供非常方便的錯誤處理方式,在你可能會出錯的步驟點擊右鍵選擇Define Error handing , 它會要求你指定一個處理error 的步驟,你可以使用文本文件或者數據庫的表來儲存這些錯誤信息,這些錯誤信息會包含一個id 和一個出錯的字段,當你得到這些錯誤信息之後就需要你自己分析出錯的原因了,比如違反主鍵約束可能是你生成主鍵的方式有錯誤或者本身的數據有重複,而違反外鍵約束則可能是你依賴的一些表裏面的數據還沒有轉換或者外鍵表本身過濾掉了這些數據. 當你調整了這些錯誤之後,確定所有依賴的數據都被正確的處理了.kettle user guide 裏面有更詳細的解釋,裏面還附帶了一個使用javascript 來處理錯誤的示例,這種方式可以作爲處理簡單數據質量的方式.

18. 文檔,文檔,文檔
Kettle 提供了豐富的文檔和使用手冊,小到一個數據庫連接怎麼連,大到一個功能怎麼實現,所有的參數列表,對話框的每一個輸入輸出代表什麼意思都有解釋,所以當你遇到問題你應該第一時間翻閱這些文檔,也許上面已經告訴你怎麼做了. 另外kettle 還有一個非常活躍的社區,你可以到上面提問,但是記住在你提問之前先搜索一下論壇看有沒有類似的問題已經問過了,如果沒有記得描述清楚你的問題

總結
本系列文章主要討論瞭如何使用kettle 來處理數據倉庫中的緩慢增長維,動態ETL如何設計,增量更新的一些設計技巧,在應用程序中如何集成kettle 以及在使用kettle 時的一些常見問題. 如果你正在尋找一個工具來幫助你解決數據庫的集成問題或是你打算建立一個商業智能項目的數據倉庫,那麼kettle是一個不錯的選擇,你不用支付任何費用就可以得到很多很多數據集成的特性,大量文檔和社區支持. 難道這些不就是你希望從一個商業工具上的到的嗎?還在等什麼 ,開始你的數據集成之旅吧

開源ETL工具kettle系列之在應用程序中集成

摘要:本文主要討論如何在你自己的Java應用程序中集成Kettle

如果你需要在自己的Java應用程序中集成Kettle , 一般來說有兩種應用需求,一種是通過純設計器來設計ETL轉換任務,然後保存成某種格式,比如xml或者在數據庫中都可以,然後自己調用程序解析這個格式,執行這種轉換,是比較抽象的一種執行方式,ETL裏面轉換了什麼東西我們並不關心,只關心它有沒有正常執行。另一種是通過完全編程的方式來實現,詳細的控制每一個步驟,需要知道轉換執行的成功與否,這種方式可能需要更多的理解kettle的API 以便更好的跟你的應用程序緊密結合,不過難度也比較大,可以很好的定製你的應用程序,代價自然是入門門檻比較高。本文主要向你解釋第一種Kettle的集成方式,文中所列出的代碼節選自pentaho ,不過應用程序本身跟pentaho 沒有什麼關係。
    Pentaho 集成kettle的代碼主要是兩個類,KettleSystemListener和 KettleComponent,看名字就猜出KettleSystemListener 主要是起監聽器的作用,它主要負責初始化kettle的一些環境變量,這個類主要包含四個方法: startup() , readProperties(),environmentInit(),shutdown(),程序入口自然是startup()方法,然後它會調用 environmentInit() 方法,這個方法就調用readProperties()方法讀一個配置文件kettle.properties,這個文件主要記錄者kettle運行時可以調用的一些環境變量,關於kettle.properties文件怎麼用,第二篇文章“使用Kettle設計動態轉換”有提到,readProperties()方法讀完這個文件之後就把裏面的鍵值對轉換成變量傳給kettle運行環境.當kettle運行完了之後就調用 shutdown()方法結束轉換. KettleSystemListener相對邏輯比較簡單,就不多介紹,下面主要介紹重點類:
KettleComponent
KettleComponent的方法主要有三種類型,一類是用來初始化工作,做一些驗證工作,第二類是執行轉換的方法,也是主要需要討論的方法,第三類是取得數據結果的,有時候你需要得到轉換的結果交給下一個步驟處理.下面分別討論這三類方法。

初始化
   KettleComponent的初始化工作主要是驗證這個轉換,包括有 validateSystemSettings(),init(),validateAction(),全部都是public 方法,validateSystemSettings()會檢查kettle 使用何種方式來連接資源庫。
kettle有兩種方式連接資源庫,一種是純數據庫式,也就是你所有的轉換全部都保存在一個數據庫中,一般你在開始使用kettle的時候,它都會要求你建立一個資源倉庫,這個資源倉庫的連接方式就是你的數據庫連接,你需要能夠有相應的數據庫驅動和對應的連接用戶名和密碼。另外一種連接方式是使用文本文件,也就是xml文件,在做完任何轉換之後,我們都可以把轉換或者Job變成xml文件輸出,這個輸出文件包含你所有轉換的全部信息。
在示例應用中使用的是文件的連接方式,下面看一下初始化的一段代碼:
Boolean useRepository = PentahoSystem.getSystemSetting("kettle/settings.xml",
                             "repository.type","files").equals("rdbms");
PentahoSystem.getSystemSetting()方法只是返回一個字符串,使用的xpath讀一個xml的對應字段,下面列出settings.xml文件:
<kettle-repository>
  <!-- The values within <properties> are passed directly to the Kettle Pentaho components. -->      
 <!-- This is the location of the Kettle repositories.xml file, leave empty if the default is used: $HOME/.kettle/repositories.xml -->
 <repositories.xml.file></repositories.xml.file>
 <repository.type>files</repository.type>
 <!--  The name of the repository to use -->
 <repository.name></repository.name> 
 <repository.userid>admin</repository.userid> 
 <repository.password>admin</repository.password>  
</kettle-repository>
可以看到其中的repositories.xml.file 上面的一段註釋,如果這個值爲空會默認使用$HOME/.kettle/repository.xml文件當作資源庫的連接文件,由於示例中使用的是文本文件所以沒有用數據庫連接,下面的repository.userid和repository.password是指的kettle的資源庫連接的用戶名和密碼,一般默認安裝就兩個,admin/admin  和guest/guest , 這裏的用戶名和密碼不是連接數據庫的用戶名和密碼,連接數據庫的用戶名和密碼是在另外一個文件repositories.xml.file指定的值所定義的
一般默認的kettle安裝並且運行了一段時間之後,會在$HOME/.kettle 目錄下創建一些文件,如果你要在自己的系統中集成kettle的話,也需要保留這些文件,當然不一定位置是在原來的位置,關鍵是要讓kettle知道這些文件放在哪。

執行轉換
當讀完了這些配置文件並且驗證了之後,KettleComponent就開始把前面讀到的轉換文件或者資源庫類型變成Kettle的API,這主要是在executeAction()方法裏面進行,它當然根據連接方式也分兩種執行類型:
1. 文本執行方式
2. 資源庫連接方式

文本執行方式需要接受一個你指定的運行轉換的文件或者Job的文件,然後把這個xml文件解析成Kettle能夠執行的模式,
根據執行的類型又可以分成兩種:
1. Trans任務
2. Job任務
兩個執行的邏輯差不多,下面先介紹Trans的執行方式:

執行Trans任務
transMeta = new TransMeta(fileAddress, repository, true);
      transMeta.setFilename(fileAddress);
然後它會調用:
executeTransformation(TransMeta transMeta, LogWriter logWriter)
這個方法是真正的把前面的來的transMeta轉換成trans對象,等待下一步的執行:
Trans trans = new Trans(logWriter, transMeta);
List stepList = trans.getSteps();
for (int stepNo = 0; stepNo < stepList.size(); stepNo++) {
      StepMetaDataCombi step = (StepMetaDataCombi) stepList.get(stepNo);
      if (step.stepname.equals(stepName)) {                 
   ①          Row row = transMeta.getStepFields(stepName);
              // create the metadata that the Pentaho result set needs
              String fieldNames[] = row.getFieldNames();
              String columns[][] = new String[1][fieldNames.length];
              for (int column = 0; column < fieldNames.length; column++) {
                    columns[0][column] = fieldNames[column];
              }
    ②        MemoryMetaData metaData = new MemoryMetaData(columns, null);
              results = new MemoryResultSet(metaData);
              // add ourself as a row listener
    ③       step.step.addRowListener(this);
             foundStep = true;
             break;
      }
}
1. Row對象是kettle用來表示一行數據的標準對象,跟jdbc取出來的一條數據轉化後成爲的一個POJO是一樣的。裏面可以包含多個字段。
2 . MemoryMetaData對象是pentaho特有的,是專門用來返回ETL任務執行後的結果的,與標準的JDBC裏面的resultSet 對應的resultSetMetaData  是一樣的。
3. 對於如何處理數據的一個Listener,實現的是一個RowListener,數據是每一行每一行處理的,後面會介紹如果需要輸出數據怎麼取得這些輸出數據。如果不需要放回任何對象,則從1處開始都可以不要,只要初始化step對象即可。

所有的step對象都已經初始化之後就可以開始執行了,
trans.startThreads();
trans.waitUntilFinished();
結束之後還有一些清理工作就不列出了。

執行Job任務
執行Job任務之前還是會讀取Job任務的描述文件,然後把這個描述文件(kettle的 .ktr文件)變成一個xml文檔的dom :
org.w3c.dom.Document doc = XmlW3CHelper.getDomFromString(jobXmlStr);
之後也是初始化對應的元數據對象JobMeta
jobMeta = new JobMeta(logWriter, doc.getFirstChild(), repository);
得到了jobMeta 之後就可以執行這個Job了,這裏跟trans是一樣的。
job = new Job(logWriter, StepLoader.getInstance(), repository, jobMeta);
由於Job一般都沒有什麼返回值,所以Job不需要初始化它下面的對象,直接開始運行就可以了
job.start();
job.waitUntilFinished(5000000);

連接資源庫
連接資源庫使用的是connectToRepository()方法,先取得RepositoriesMeta對象,然後根據你在setting.xml文件裏面定義的repository的名字來連接對應的repository.理論上來說我們一般都只使用一個 repository ,但如果在產品中需要使用多個repository的話,你需要自己配置多個repository的名字和對應的用戶名和密碼。只列出幾行關鍵代碼,
repositoriesMeta = new RepositoriesMeta(logWriter);
repositoriesMeta.readData(); // 從$HOME/.kettle/repositories.xml 讀數據.
repositoryMeta = repositoriesMeta.findRepository(repositoryName);
repository = new Repository(logWriter, repositoryMeta, userInfo);
userInfo = new UserInfo(repository, username, password);

從資源庫讀取Trans
連接到資源庫之後自然是想辦法讀取數據庫的表,把裏面的記錄轉換成爲Trans 對象,使用的是loadTransformFromRepository,這個方法的函數原型需要解釋一下:
TransMetaloadTransformFromRepository(String directoryName, String transformationName, Repository repository,LogWriter logWriter)
第一個參數String directoryName 代表是你儲存轉換的目錄,當你使用kettle 圖形界面的時候,點擊repository菜單的explorer repository , 你會發現你所有的東西都是存儲在一個虛擬的類似與目錄結構的地方,其中包括database connections , transformations , job , users 等,所以你需要的是指定你連接的目錄位置,你也可以在目錄裏面再創建目錄。
String transformationName 自然指的就是你轉換的名字.
Repository repository 指的是你連接的資源庫。
LogWriter logWriter 指定你的日誌輸出,這個log 指的是你kettle 轉換的日誌輸出,不是應用程序本身的輸出。
讀取TransMeta的步驟也相對比較簡單
repositoryDirectory=repository.getDirectoryTree().findDirectory(directoryName);
transMeta = new TransMeta(repository, transformationName, repositoryDirectory);

從資源庫讀取Job
從資源庫讀取Job跟Trans 的步驟基本是一樣的,同樣需要指定你存儲Job的目錄位置.
JobMeta loadJobFromRepository(String directoryName, String jobName,
Repository repository, LogWriter logWriter)

讀取結果集
一般Job都是不會返回任何結果集的,大部分Trans也不會返回結果集,應爲結果集一般都會直接從一個數據庫到另一個數據庫了,但是如果你需要返回轉換的結果集,那麼這一小結將會向你解釋如何從一個Trans裏面讀取這些結果集
首先,你需要一個容納Result的容器,就是類似與JDBC裏面的resultSet, resultSet當然會有一個resultSetMetadata跟它相關聯,在本文所舉的實例中,使用的是pentaho私有的memoryResultSet,
你可以不用關心它的細節,並且它的類型正如它的名字一樣是存在與Memory的,所以它不能被持久化,這個裏面儲存的是一個二維的Object數組,裏面的數據就是從kettle轉化之後來的。
要從kettle的轉換中讀取結果集,要實現RowListener 接口,Row 是kettle裏面表示一行數據的一個類,RowListener 自然是指在轉換數據轉換的時候發生的事件,它有三個方法需要實現,
void rowReadEvent(Row)
void rowWrittenEvent(Row)
void errorRowWrittenEvent(Row)
分別對應讀數據時的事件,寫數據事的時間,出錯時的時間,我們需要取得結果集,所以只需要實現rowWrittenEvent(Row)就可以了,Row對象是通過TransMeta取得的,
Row row = transMeta.getStepFields(stepName);
下面給出具體實現取Row轉換成resultSet的代碼:
Object pentahoRow[] = new Object[results.getColumnCount()];
    for (int columnNo = 0; columnNo < results.getColumnCount(); columnNo++) {
      Value value = row.getValue(columnNo);
      switch (value.getType()) {
        case Value.VALUE_TYPE_BIGNUMBER:
          pentahoRow[columnNo] = value.getBigNumber();
          break;
    ........

results.addRow(pentahoRow);
默認的數據類型是String 類型(在省略部分).
整個代碼最重要的一行是Value value = row.getValue(columnNo);
這是真正取得實際數據的一行。有時候你會覺得實現一個resultSet比較麻煩,尤其是你還要實現相關的resultSetMetaData,怎麼把數據轉換成你自己的類型,你大可以就用一個List of List 來實現,裏面的List 就代表Row 的對應數據,外面一層List 就是result , 整個代碼會簡單一些,當然,你要自己知道最後這個List怎麼用.

本文有意隱藏了一些跟pentaho有關的細節,比如 validateSystemSettings(),init(),validateAction()方法,這些都是pentaho私有的,有些方法比如 rowWrittenEvent(Row) 是用來取結果集的,但是很多時候我們不需要取轉換的結果集,文中很多代碼都只列出主要的部分,省略一些判斷,調試,log部分的代碼,大家可以自己下載這些代碼來研究,
本文並沒有給出一個可以獨立運行的示例,因爲這個示例一定會太過於簡單(不超過15行代碼),但是卻並不能考慮到各種情況,連接資源庫還是文件,運行轉換還是Job ,metadata怎麼得來的,需不需要轉換之後的結果。
關於在本文一開始提到的使用kettle的兩種方式,對於第二種使用方式:使用完全編程的方式來運行轉換,其實它的與第一種方式的區別就好像一個用設計器來寫xml文件,一個用純手工方式寫xml文件(用代碼的xml),大家可以參考官方網站上的一段示例代碼,地址如下:
http://kettle.pentaho.org/downloads/api.php

開源ETL工具kettle系列之增量更新設計

ETL中增量更新是一個比較依賴與工具和設計方法的過程,Kettle中主要提供Insert / Update 步驟,Delete 步驟和Database Lookup 步驟來支持增量更新,增量更新的設計方法也是根據應用場景來選取的,雖然本文討論的是Kettle的實現方式,但也許對其他工具也有一些幫助。本文不可能涵蓋所有的情況,歡迎大家討論。

應用場景
增量更新按照數據種類的不同大概可以分成:
1. 只增加,不更新,
2. 只更新,不增加
3. 即增加也更新
4. 有刪除,有增加,有更新
其中1 ,2, 3種大概都是相同的思路,使用的步驟可能略有不同,通用的方法是在原數據庫增加一個時間戳,然後在轉換之後的對應表保留這個時間戳,然後每次抽取數據的時候,先讀取這個目標數據庫表的時間戳的最大值,把這個值當作參數傳給原數據庫的相應表,根據這個時間戳來做限定條件來抽取數據,抽取之後同樣要保留這個時間戳,並且原數據庫的時間戳一定是指定默認值爲sysdate當前時間(以原數據庫的時間爲標準),抽取之後的目標數據庫的時間戳要保留原來的時間戳,而不是抽取時候的時間。
   對於第一種情況,可以使用Kettle的Insert / Update 步驟,只是可以勾選Don’t perform any update選項,這個選項可以告訴Kettle你只會執行Insert 步驟。
對於第二種情況可能比較用在數據出現錯誤然後原數據庫有一些更新,相應的目標數據庫也要更新,這時可能不是更新所有的數據,而是有一些限定條件的數據,你可以使用Kettle的Update 步驟來只執行更新。關於如何動態的執行限定條件,可以參考前一篇文章。
第三種情況是最爲常見的一種情況,使用的同樣是 Kettle的Insert / Update 步驟,只是不要勾選Don’t perform any update 選項。
第四種情況有些複雜,後面專門討論。

對於第1,2,3種情況,可以參考下面的例子。
這個例子假設原數據庫表爲customers , 含有一個id , firstname , lastname , age 字段,主鍵爲id , 然後還加上一個默認值爲sysdate的時間戳字段。轉換之後的結果類似:id , firstname , lastname , age , updatedate . 整個設計流程大概如下:

 
                                                                        圖1
其中第一個步驟的sql 大概如下模式:
Select max(updatedate) from target_customer ;
你會注意到第二個步驟和第一個步驟的連接是黃色的線,這是因爲第二個table input 步驟把前面一個步驟的輸出當作一個參數來用,所有Kettle用黃色的線來表示,第二個table input 的sql 模式大概如下:
Select field1 , field2 , field3 from customers where updatedate > ?
後面的一個問號就是表示它需要接受一個參數,你在這個table input 下面需要指定replace variable in script 選項和execute for each row 爲選中狀態,這樣,Kettle就會循環執行這個sql , 執行的次數爲前面參數步驟傳入的數據集的大小。
 
                                                                            圖2

關於第三個步驟執行insert / update 步驟需要特別解釋一下,
 
                                                                                    圖3

Kettle執行這個步驟是需要兩個數據流對比,其中一個是目標數據庫,你在Target table 裏面指定的,它放在The keys to look up the values(s) 左邊的Table field 裏面的,另外一個數據流就是你在前一個步驟傳進來的,它放在The keys to look up the value(s) 的右邊,Kettle首先用你傳進來的key 在數據庫中查詢這些記錄,如果沒有找到,它就插入一條記錄,所有的值都跟你原來的值相同,如果根據這個key找到了這條記錄,kettle會比較這兩條記錄,根據你指定update field 來比較,如果數據完全一樣,kettle就什麼都不做,如果記錄不完全一樣,kettle就執行一個update 步驟。所以首先你要確保你指定的key字段能夠唯一確定一條記錄,這個時候會有兩種情況:
1.維表
2.事實表
維表大都是通過一個主鍵字段來判斷兩條記錄是否匹配,可能我們的原數據庫的主鍵記錄不一定對應目標數據庫中相應的表的主鍵,這個時候原數據庫的主鍵就變成了業務主鍵,你需要根據某種條件判斷這個業務主鍵是否相等,想象一下如果是多個數據源的話,業務主鍵可能會有重複,這個時候你需要比較的是根據你自定義生成的新的實際的主鍵,這種主鍵可能是根據某種類似與sequence 的生成方式生成的,
事實表在經過轉換之後,進目標數據庫之前往往都是通過多個外鍵約束來確定唯一一條記錄的,這個時候比較兩條記錄是否相等都是通過所有的維表的外鍵決定的,你在比較了記錄相等或不等之後,還要自己判斷是否需要添加一個新的主鍵給這個新記錄。
上面兩種情況都是針對特定的應用的,如果你的轉換過程比較簡單,只是一個原數據庫對應一個目標數據庫,業務主鍵跟代理主鍵完全相同的時候完全可以不用考慮這麼多。

有刪除,有增加,有更新
首先你需要判斷你是否在處理一個維表,如果是一個維表的話,那麼這可能是一個SCD情況,可以使用Kettle的Dimension Lookup 步驟來解決這個問題,如果你要處理的是事實表,方法就可能有所不同,它們之間的主要區別是主鍵的判斷方式不一樣。
事實表一般都數據量很大,需要先確定是否有變動的數據處在某一個明確的限定條件之下,比如時間上處在某個特定區間,或者某些字段有某種限定條件,儘量最大程度的先限定要處理的結果集,然後需要注意的是要先根據id 來判斷記錄的狀態,是不存在要插入新紀錄,還是已存在要更新,還是記錄不存在要刪除,分別對於id 的狀態來進行不同的操作。
處理刪除的情況使用 Delete步驟,它的原理跟Insert / Update 步驟一樣,只不過在找到了匹配的id之後執行的是刪除操作而不是更新操作,然後處理Insert / Update 操作,你可能需要重新創建一個轉換過程,然後在一個Job 裏面定義這兩個轉換之間的執行順序。
如果你的數據變動量比較大的話,比如超過了一定的百分比,如果執行效率比較低下,可以適當考慮重新建表。
另外需要考慮的是維表的數據刪除了,對應的事實表或其他依賴於此維表的表的數據如何處理,外鍵約束可能不太容易去掉,或者說一旦去掉了就可能再加上去了,這可能需要先處理好事實表的依賴數據,主要是看你如何應用,如果只是簡單的刪除事實表數據的話還比較簡單,但是如果需要保留事實表相應記錄,可以在維表中增加一條記錄,這條記錄只有一個主鍵,其他字段爲空,當我們刪除了維表數據後,事實表的數據就更新指向這條空的維表記錄。

定時執行增量更新
可能有時候我們就是定時執行更新操作,比如每天或者一個星期一次,這個時候可以不需要在目標表中增加一個時間戳字段來判斷ETL進行的最大時間,直接在取得原數據庫的時間加上限定條件比如:
Startdate > ? and enddate < ?
或者只有一個startdate
Startdate > ?   (昨天的時間或者上個星期的時間)
這個時候需要傳一個參數,用get System Info 步驟來取得,而且你還可以控制時間的精度,比如到天而不是到秒的時間。
當然,你也需要考慮一下如果更新失敗了怎麼處理,比如某一天因爲某種原因沒有更新,這樣可能這一天的記錄需要手工處理回來,如果失敗的情況經常可能發生,那還是使用在目標數據庫中增加一個時間字段取最大時間戳的方式比較通用,雖然它多了一個很少用的字段。

執行效率和複雜度
刪除和更新都是一項比較耗費時間的操作,它們都需要不斷的在數據庫中查詢記錄,執行刪除操作或更新操作,而且都是一條一條的執行,執行效率低下也是可以預見的,儘量可能的縮小原數據集大小。減少傳輸的數據集大小,降低ETL的複雜程度

時間戳方法的一些優點和缺點
優點:  實現方式簡單,很容易就跨數據庫實現了,運行起來也容易設計
缺點: 浪費大量的儲存空間,時間戳字段除ETL過程之外都不被使用,如果是定時運行的,某一次運行失敗了,就有可能造成數據有部分丟失.

其他的增量更新辦法:
增量更新的核心問題在與如何找出自上次更新以後的數據,其實大多數數據庫都能夠有辦法捕捉這種數據的變化,比較常見的方式是數據庫的增量備份和數據複製,利用數據庫的管理方式來處理增量更新就是需要有比較好的數據庫管理能力,大多數成熟的數據庫都提供了增量備份和數據複製的方法,雖然實現上各不一樣,不過由於ETL的增量更新對數據庫的要求是隻要數據,其他的數據庫對象不關心,也不需要完全的備份和完全的stand by 數據庫,所以實現方式還是比較簡單的.,只要你創建一個與原表結構類似的表結構,然後創建一個三種類型的觸發器,分別對應insert , update , delete 操作,然後維護這個新表,在你進行ETL的過程的時候,將增量備份或者數據複製停止,然後開始讀這個新表,在讀完之後將這個表裏面的數據刪除掉就可以了,不過這種方式不太容易定時執行,需要一定的數據庫特定的知識。如果你對數據的實時性要求比較高可以實現一個數據庫的數據複製方案,如果對實時性的要求比較低,用增量備份會比較簡單一點。

幾點需要注意的地方:
1.觸發器
無論是增量備份還是數據複製,如果原表中有觸發器,在備份的數據庫上都不要保留觸發器,因爲我們需要的不是一個備份庫,只是需要裏面的數據,最好所有不需要的數據庫對象和一些比較小的表都不用處理。
2.邏輯一致和物理一致
數據庫在數據庫備份和同步上有所謂邏輯一致和物理一致的區別,簡單來說就是同一個查詢在備份數據庫上和主數據庫上得到的總的數據是一樣的,但是裏面每一條的數據排列方式可能不一樣,只要沒有明顯的排序查詢都可能有這種情況(包括group by , distinct , union等),而這可能會影響到生成主鍵的方式,需要注意在設計主鍵生成方式的時候最好考慮這一點,比如顯式的增加order 排序. 避免在數據出錯的時候,如果需要重新讀一遍數據的時候主鍵有問題.

總結
    增量更新是ETL中一個常見任務,對於不同的應用環境可能採用不同的策略,本文不可能覆蓋所有的應用場景,像是多個數據源匯到一個目標數據庫,id生成策略,業務主鍵和代理主鍵不統一等等,只是希望能給出一些思路處理比較常見的情況,希望能對大家有所幫助。

 開源ETL工具kettle系列之動態轉換

摘要:本文主要討論使用Kettle來設計一些較爲複雜和動態的轉換可能使用到的一些技巧,這些技巧可能會讓你在使用Kettle的時候更加容易的設計更強大的ETL任務。

動態參數的傳遞
Kettle 在處理運行時輸入參數可以使用JavaScript 來實現,大部分工作只是按照一個模板來處理的
動態參數傳遞主要使用在像數據清理,調式,測試,完成複雜的條件過濾等等,這種方式一般不會在產品已經運行穩定了一段時間之後使用,因爲我們一般仍然是做定時任務來自動轉換數據,所以在開始介紹如何使用動態參數之前,希望大家能明白不要在產品數據庫上做實驗,即使你已經知道你的轉換有什麼影響並且做了備份,因爲這種方法是不可能自動執行的。
Kettle有兩種動態參數傳遞的方法,一種是非常輕量級的傳argument , 另一種是對付較複雜一點情況使用JavaScript . 下面分別介紹這兩種方法。
1. argument
當你在運行一個轉換的時候,不管這個轉換是一個Job的一部分還是隻有這個轉換,你都可以傳遞參數給它,當你運行一個轉換的時候,會彈出一個 Execution a Transformation 的對話框,讓你選擇執行轉換的方式,本地執行,遠程執行,分佈式執行,下面就是日誌記錄的級別和回放時間,然後是argument 和 variables 的設定。Argument 和 variables 的區別在官方FAQ裏面也有解釋。你也可以參考一下官方的解釋和下面解釋的異同。
Q : Argument 和 variables 的區別 /
A : variables 也可以認爲叫做environment variables , 就像它的名字一樣,主要是用來設定環境變量的,比如最常見的:文件的存放地址,smtp的配置等等,你也可以把它認爲是編程語言裏面的全局變量,即使是不同的轉換它們也擁有同樣的值,而argument 自然就類似與局部變量,只針對一個特定的轉換,比如像是限定結果集的大小和過濾條件。

取得argument的值
我們在轉換之前設置了argument的值,需要用到的時候就使用get system info 步驟,這個步驟取得在運行時參數,需要注意的是我們是先設置get system info ,然後在裏面決定要使用多少個參數,最多10個,每個參數名叫什麼,然後我們才能在運行時看到你設置了的參數名後面跟一個要你輸入的值,並且參數類型是不能夠指定,全部都當作字符串處理,如果你需要對參數類型有要求,你需要自己轉換,使用一個Mapping步驟或者Select values步驟。
取得variable的值
Variable 的值個數不受限制,你可以在kettle菜單的set environment裏面設置,也可以使用文件儲存這些值,在第一次運行kettle之後,kettle會在%HOME_USER_FOLDER%菜單裏面創建一個 .kettle文件夾,如果是windows 用戶可能就是C:/Documents and Settings/${your user name}/.kettle這個文件夾,如果是linux用戶可能就是/home/${your user name }/.kettle文件夾,這個文件夾下面有kettle.properties文件,如果你打開這個文件,你會發現裏面有一些以#開頭的註釋,其中設置了一些像是:PRODUCTION_SERVER = Hercules 這樣的鍵值對,你可以自己定義一些環境變量比如像是smtp的地址,ftp服務器的地址,你放log文件的目錄名等等,當然不能直接編輯這個文件就設置環境變量,要先設置KETTLE_HOME環境變量,windows就是點我的電腦,然後在設置path的那個地方添加一個KETTLE_HOME變量,linux就是export KETTLE_HOME=’一個目錄’,這個目錄可以任意地方,不過一般還是指向kettle的安裝目錄或是你自己的文檔目錄,然後啓動kettle它會創建一個新的.kettle目錄,編輯裏面的kettle.properties文件就可以設置環境變量了.


2. 使用腳本
Kettle使用的是JavaScript來作爲它的腳本實現,使用的是mozilla 的rhino 1.5r5版本實現,如果你打算實現一些複雜的計算過程,比如字符串分割,數據類型轉換,條件計算等等,你都應該使用腳本語言來搞定。
我們在某種應用環境下使用腳本語言來實現一些動態的功能大部分原因都是爲了避免編程,一個複雜一點的應用程序,比如像是Kettle這種工具,或是報表工具,它們不可能提供全部功能,把什麼都做成圖形化,應用條件永遠都是複雜的,如果你不想研究代碼和程序的結構,甚至你都不知道怎樣編程,腳本語言絕對是一種簡單的解決方案,而JavaScript語言又是其中入門門檻非常低的一種,你完全可以多看一些例子,嘗試模仿一些腳本來解決問題,也許會有一點難以調試和測試,但總比自己編程要好的多。
下面的這個例子將會使用JavaScript彈出一個對話框來接受兩個參數,都是時間類型,其中的UI組件是使用的swt 的一些類,Kettle使用的是swt 作爲其UI組件,如果你對swt 有了解的話會更容易理解這些UI組件,當然這並不需要你有swt 編程的經驗或者其他GUI設計的經驗。
打開Kettle 下載目錄下的samples / transformation / JavaScript dialog.ktr 文件(使用Kettle File 菜單裏面的import from an xml file 。你會看到一個包含3個步驟的轉換。
第一個步驟使用generate rows 產生一條測試數據,測試數據包含一個DateFromProposal 時間字段和一個DateToProposal時間字段。
第二個步驟使用JavaScript 來實現動態的參數轉變,它會連續彈出兩次對話框,要求輸入一個起始值和結束值,然後它會調用一些JavaScript 函數來對日期格式做一些處理,
第三個步驟使用Dummy 來接受輸入,你完全可以使用File output 步驟來查看輸出。

我們先看一下第二部中的JavaScript代碼:(刪掉了開頭的註釋)
var display;
var displayHasToBeDisposed=false;
var shell=null;

try {
    display=Packages.org.eclipse.swt.widgets.Display.getCurrent();
    shell=display.getActiveShell();
} catch(e) {
    // if it runs in batch mode (Pan or preview mode) no Display is available, so we have to create one
    display=new Packages.org.eclipse.swt.widgets.Display();
    displayHasToBeDisposed=true;
    shell=new Packages.org.eclipse.swt.widgets.Shell(display);
}

// if we run in Pan we need to load the properties:
if(!Packages.org.pentaho.di.ui.core.PropsUI.isInitialized()) {
    Packages.org.pentaho.di.ui.core.PropsUI.init(display,2); //2=TYPE_PROPERTIES_PAN
}

var dateDefaultFrom=DateFromProposal.getString().substr(0,10); //only the date and not the time
var dialogDateFrom=new Packages.org.pentaho.di.ui.core.dialog.EnterTextDialog(shell, "Date from", "Please enter the beginning date", dateDefaultFrom);
var dateFromAsString=dialogDateFrom.open();

if(dateFromAsString!=null && dateFromAsString.length()>0) {
    var dateDefaultTo=DateToProposal.getString().substr(0,10); //only the date and not the time;
    var dialogDateTo=new Packages.org.pentaho.di.ui.core.dialog.EnterTextDialog(shell, "Date to", "Please enter the ending date", dateDefaultTo);
    var dateToAsString=dialogDateTo.open();
    if(dateToAsString!=null && dateToAsString.length()>0) {
        // here you could check or change formats a.s.o
    } else {
        // stop transformation when user cancels
        throw new Packages.java.lang.RuntimeException("Input canceled by the user.");
    }
} else {
    // stop transformation when user cancels
    throw new Packages.java.lang.RuntimeException("Input canceled by the user.");
}

if(displayHasToBeDisposed) {
  display.dispose();
}
Display 和 shell 都是swt 裏面的對象,你只用知道他們是表示UI的就可以了.
DateFromProposal和DateToProposal都是前面傳過來的字段,dateFromAsString和dateToAsString都是需要輸出的內容,整個腳本只是簡單的取了兩個日期變量的時間部分,使用了字符串操作的substr()函數。
其中有三點需要注意:
1. dialog對象的初始化方式:使用的構造函數類型爲
EnterTextDialog(Shell parent, String title, String message, String text) , 另一種構造函數類型是加一個參數fixed :
EnterTextDialog(Shell parent, String title, String message, String text, boolean fixed)
fixed代表字體是否用固定寬度,text參數代表的是輸入在對話框裏面的值,一般可以默認爲空或輸入一段用戶提示信息,例子中是設置成原先轉換之前的值,相當於默認值。
2. 使用open()函數取得輸入值
我們調用dialog 的open()函數取得輸入的值。
3. 異常的處理方式
基本上是一個標準的java 語法的try catch throw .
最後運行一下並查看輸出,運行的時候什麼都不輸入接受默認值就可以了,最後查看輸出,以下是文本方式的輸出,以分號分割
DateFromProposal;DateToProposal;dateFromAsString;dateToAsString
2006/01/01 00:00:00.000;2006/12/31 00:00:00.000;2006/01/01;2006/12/31

最後需要注意的是這種方式的實現可能將來會直接用一個新的step來實現,不用這樣寫腳本。

調試
調試可不是程序的專利,ETL過程同樣需要調試過程,Kettle同樣支持比較簡單的調試過程,你可能已經發現了在菜單下面的工具欄下面有一個debug 和preview 按鈕來支持調試過程,這種調試的技巧同樣可以用來幫助你完成一些複雜的ETL工程,下面以一個例子來解釋調試過程.
首先,打開samples / add sequence specify a common counter.ktr 文件,你會發現一個定義了兩個sequence 的轉換,點擊debug按鈕,它會彈出一個Transformation debug dialog 窗口。
 
                                                                          圖1

這個窗口左邊列出了在這個轉換中所有的步驟,我們選取Generate ID 步驟,然後設置斷點的條件:
Kettle支持兩種斷點的方法,一種基於限定結果集的數量大小,另一種是基於條件的判斷過程。
我們選擇基於結果集大小的方式,只查看前面5條數據。
你會看到Kettle列出了Generate ID 步驟產生的前面5條數據,
 
                                      圖2

從上圖中可以看到這個generate id 步驟產生了5個值並不是連續的,下面的按鈕Close ,Stop 可以控制當前線程是繼續還是停止.

利用調試的方法可以幫住我們設計一些需要基於條件判斷的複雜ETL過程,我們使用調試的方法來查看數據中是否可能存在某些特定數據,以此來設計一些ETL過程針對這些數據進行處理。

開源ETL工具kettle系列之建立緩慢增長維

摘要:本文主要介紹使用kettle 來建立一個Type 2的Slowly Changing Dimension 以及其中一些細節問題

1. Kettle 簡介
Kettle 是一個強大的,元數據驅動的ETL工具被設計用來填補商業和IT之前的差距,將你公司的數據變成可增長的利潤.

我們先來看看Kettle能做什麼:
1. Data warehouse population with built-in support for slowly changing dimensions, junk dimensions and much, much more.
2. Export of database(s) to text-file(s) or other databases
3. Import of data into databases, ranging from text-files to excel sheets
4. Data migration between database applications
5. Exploration of data in existing databases. (tables, views, synonyms, )
6. Information enrichment by looking up data in various information stores (databases, text-files, excel sheets, )
7. Data cleaning by applying complex conditions in data transformations
8. Application integration

本系列文章主要介紹如下幾點:
1. 數據倉庫內建支持緩慢增長維SCD ,
2. 在數據轉換中使用複雜條件判斷來清理數據
3. 如何使用kettle 來處理增量更形
4. 將Kettle 集成到你的應用程序裏
5. 使用kettle中應該注意的一些地方
2. Kettle 文檔
最好的kettle教程就在你身邊,我們下載的kettle-version. zip 文件裏其實已經包括了非常多的示例和文檔,在你的kettle文件夾下,docs 文件夾下包含了所有的文檔,samples文件夾下包含了一些示例,後面的介紹中一部分示例都來自kettle自帶的這個示例文件夾下。docs裏面最主要的是Spoon-version-User-Guide. zip ,裏面記錄了kettle 的技術性文檔,包括支持的操作系統,數據庫平臺,文本格式,圖形化的界面,其中最重要的是所有的轉換對象(Transformation Core Objects) 和Job對象(Job Core Objects) 的解釋,包括截圖和每一個參數的解釋。

3. Kettle與Slowly Changing Dimension
  我們使用kettle自帶的samples文件下的示例,來看kettle如何支持SCD的。
打開samples / jobs / Slowly Changing Dimension 文件夾,發現裏面有三個文件,
create - populate - update slowly changing dimension.kjb
DimensionLookup - update dimension table 2.ktr
DimensionLookup - update dimension table.ktr
其中後綴以 .kjb 結尾的是kettle 的job 文件導出的格式,而以ktr 結尾的是kettle 的transformation 導出的格式,打開其中的DimensionLookup - update dimension table.ktr , 出現如下所示 :
 
    圖1
1. 最左邊的是產生測試數據,如果是實際環境的話應該是連接真實的數據庫,產生的真實數據格式打開如下:

    圖2
2 第二個步驟Dummy 就是把前面的數據合併起來,Dummy 步驟本身不做任何事情,不過由於前面有四個輸入指向它,所以它在第二步的作用等同於數據合併。
3 第三個步驟是取得系統參數(get system date) , 它取得當前系統時間的日期,並且格式是當天的 00:00:00 , 如圖所示


    4. 最後一步是真正的重點,執行Dimension Lookup / Update 步驟來更新和插入數據,以此來實現Type 1 ,2 ,3 的不同Slowly Changing Dimension 
   
       圖4

                                                                  圖5
   在開始介紹Dimension Lookup / Update 之前,先看看在執行這個步驟之前的輸入和輸入:
   輸入: 

字段名
數據類型
說明
id
int
前面步驟的輸入
name
Varchar(50)
前面步驟的輸入
firstname
Varchar(50)
前面步驟的輸入
updated
time
從第三步來的時間參數
輸出:
字段名
數據類型
說明
id
INT
來自輸入
name     
varchar(50)
來自輸入
firstname
varchar(50)
來自輸入
customer_tk
BIGINT
代理主鍵
version
INT
版本變更號
Date_from
datetime
有效期起始日期
Date_to
Datetime
有效期失效日期
   注意: 上圖中所使用的是mysql 5 數據庫做測試,所以數據類型一欄都是mysql 的數據類型,如果你使用其他數據庫,可能數據類型會有所不同,其中的datetime 的格式 yyyy/mon/day hh:mm:ss:sss

 
我們再來看看當我們第一次運行以後出現的數據輸出:
 
圖6
注意圖6中所有的 version 值都是 1 
Date_from 都是 1900/01/01 00:00:00.000
Date_to   都是 2199/12/31 23:59:59.000      這兩列都是根據圖4下面部分定義的
Id , name , firstname 都是測試數據,從前面步驟來的.
然後我們修改圖1中generate row 的部分數據(一共兩條),並且只有測試數據變了的情況下,我們再次運行轉換,查看數據輸出:
 
                                                                          圖7

注意到其中customer_tk 並沒有什麼變化,仍然在產生類似序列的輸出
Version 的值中出現了 2 , 並且只有在我們改變的數據中
在出現了改變的行中的date_from 變成了2007/11/28/ 00:00:00.000
在出現了改變的行中原來數據的date_to 變成了 2007-11-28 00:00:00.000
Id 列沒有變化,(變化了也沒用,圖5中的中間部分 Field 選項卡沒有選id)
Name , firstname 有兩個值變了(我們手工改變的)
Dimension Lookup / Update 參數解釋 
 
Step name
步驟的名稱,在一個轉換中必須是唯一的
Update the dimension?
當找到符合條件記錄的時候更新這條記錄,如果這個複選框沒有選擇,找到了符合條件記錄的時候就是插入新紀錄而不是更新
Connection
數據庫連接的名字
Target schema
 
Target table
要更新的維表的名稱
Commit size
批處理更新的記錄數
Cache size in rows
這是把維表的數據放在緩存中用來提高數據查找速度從而減少數據庫查詢的次數
注意只有最近一次的記錄會被放在緩存中,如果記錄數超過緩存大小,最有最有關的最近的最高版本號記錄會被放在緩存中
如果把cache size 設置成0 ,kettle會一直把記錄放在緩存中直到JVM沒有內存了,如果你這樣設置要確保維的記錄數不要太大
設置成 1 表示不使用緩存
Keys tab
設置在流中的主鍵和目標維表的業務主鍵,當兩個鍵相等時認爲這條記錄匹配
Fields tab
設定要更新的字段,當主鍵記錄匹配的時候,只有設定更新的字段不一樣才認爲是這條記錄是不一樣的,需要更新或者插入(注意圖5的中間部分,Fields tab 右邊設定的是Insert ,所以實現的是Type2 的SCD)
Technical key field
維的主鍵,也可以叫做代理主鍵(Surrogate Key)
Creation of technical key
指定技術主鍵的生成方式,對於你數據庫連接不適合的方式會自動被去掉,一共有三種:
1 .Use table maximum + 1 : 使用當前表最大記錄數加一的方式產生新主鍵,注意新的最大值會被緩存,所以不用每次需要產生新記錄的時候就計算
2 . Use sequence : 使用一個數據庫支持的序列來產生技術主鍵(比如Oracle ,你也可以看到圖4中這一條是灰色的因爲使用的是mysql 數據庫)
3. Use auto increment field : 使用一個數據庫支持的自動增長來產生技術主鍵(比如DB2)
Version field
使用這個字段來儲存版本號
Stream Datafield
你可以指定維記錄最後一次被更改的時間,它能指定你要更新的維的精度,如果不指定,就會默認是系統時間
Date range start field
維記錄其實有效時間
Table daterange end
維記錄失效時間
Get Fields button
指定所有你想要更新的字段,除了你指定的主鍵
SQL button
產生sql 來創建維表

官方文檔中提到的注意事項:

1. Stream date field : 如果你不想每次都改變時間的範圍,你需要添加一個額外的這個字段,比如你打算每天的午夜來進行ETL過程,可以考慮加一個Join 步驟”Yesterday 23:59:59” 作爲輸入的時間字段.
2. 這必須是一個Date 字段(不能是轉換後的字符串,即使他們有相同的格式也不行),我們(Kettle 的開發小組)把功能實現隔離出來,如果你需要的話自己要先轉換.
3. 對於Date range start and end fields : 你只能指定一個表示年的數據,而不是時間戳,如果你輸入YYYY(比如2100) ,這將會被當成一個時間戳來用: YYYY-01-01 00:00:00.000 ,(注意圖6中的格式)

另外需要注意的地方:
1. Technical key field : 其他一些ETL工具(比如OWB)也許叫做代理主鍵,只是名字上不同而已.
2. SQL Button : 當你在目標數據庫中還沒有建立維表的時候,你點擊SQL Button ,Kettle 會彈出如下對話框幫你建立維表,你會發現它默認幫你在代理主鍵和業務主鍵上建立索引。

    圖8

3 Creation of technical key : 在這個選項的第二種實現方式上,Use sequence ,這個要視你數據庫支持而定,mysql 就不支持,Oracle 支持sequence , 但是你要自己創建和管理這個sequence , 如果這個sequence 的值因某種外部因素改變了,你要自己確定sequence 產生的值處於何種狀態,如果可以的話儘量不要用,儘量用第一種:table maximum + 1 ,這種方式永遠不要擔心數據庫的不同和實現方式的不同,而且簡單易懂。

4 Stream Datefield
    4.1 這個選項是用來控制時間的精度的,有的時候我們可能只是一個月進行一次ETL,這個時候Datefield 顯然沒有必要到秒的精度,而且這個選項嚴重影響你後面如果使用緩慢增長維的sql 的複雜度,因爲你需要先把時間的精度調到你需要的精度,比如你使用的數據是到秒的精度,但是你實際需要的只是天的精度,你在sql 裏面有大量的時間都浪費在toString( stream date field) ,然後把這個字符串substring() ,執行效率會低一些.

    4.2 不要輕易改這個精度,一旦你確定了精度問題,不要嘗試改變它,尤其是當精度變細的時候,你可能會損失掉已經存在與數據庫中的數據的精度,如果你只是從 “Today 00:00:00.000” 改成 “Today 23.59.59.000” 的情況,需要手動處理好已經存在的數據格式問題.

    4.3 執行ETL的時間可能決定這個值,如果你一天可能存在5次執行ETL過程(包括自動執行或者手工執行)那麼你顯然不希望時間的精度是按天來計算的(比如Today 00.00.00這種格式)

    4.4 精度的損失並不可怕:考慮一下你的應用場景,比如我們要做表,列出2006年11月份和2006年12月份的所有銷售總和,結合上圖中的customer 的例子,假設是按客戶聚合的, 我們對於customer 的精度要求只要求到月,沒有要求到天,如果我們執行ETL的過程是一個星期執行一次,可能一個客戶在一個星期內改變了三次他的名字(雖然不是個好例子,完全是爲了配合上面的圖),而只有最後一次的改變被記錄了下來,這完全跟你執行ETL的頻度有關,但是考慮到用戶需求,只要精度到月就夠了,即使這種精度有數據損失也完全沒關係,所以你如何指定你的Stream date field 的精度主要是看用戶需求的精度。

4.5 如果以上四點你覺得只是一堆讓你頭疼的字符串,那你完全可以把stream date field 設置成空(默認的到時間戳的精度)
執行Type 2 SCD
1. “Update the dimension?” 選中
2. 在Field tabs 裏面,對於每一個你想要保持全部記錄的字段都要選擇Insert 方式.

錯誤處理和依賴問題
    如果你運行了這個轉換,你會發現你的輸出中有一條customer_tk爲1,version爲1的數據,你在圖6和圖7中沒有看到這條數據是因爲我不想一開始把這條數據跟SCD的實現混在一起,SCD的實現本身並不會告訴你要添加這條數據,這完全是跟數據建模有關係,爲了理解這個問題,我們看一下如下情況該如何處理:

    一個產品銷售的記錄是作爲一個立方體的主要事實表,它包括一個客戶維,現在因爲某種原因客戶維需要刪除掉一部分數據,但是對映的產品銷售記錄卻要保存起來,該如何處理外鍵約束的問題?

    SCD實現本身並不會考慮這個問題,因爲它跟維表沒有什麼關係,你要處理的是事實表裏面那些引用了維表的記錄,如果你沒有這個空行(它唯一的一個值就是 id ,而且是爲了滿足主鍵約束,version那個字段有沒有值不重要),事實表中的記錄就不好處理這種情況,因爲你把它賦予任何一個值都是不合適的。這種方法是爲了處理像數據依賴(外鍵的關係)和錯誤處理比較常見的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章