Kettle 系列1

文章一:ETL和Kettle簡介
ETL即數據抽取(Extract)、轉換(Transform)、裝載(Load)的過程。它是構建數據倉庫的重要環節。數據倉庫是面向主題的、集成的、穩定的且隨時間不斷變化的數據集合,用以支持經營管理中的決策制定過程。數據倉庫系統中有可能存在着大量的噪聲數據,引起的主要原因有:濫用縮寫詞、慣用語、數據輸入錯誤、重複記錄、丟失值、拼寫變化等。即便是一個設計和規劃良好的數據庫系統,如果其中存在着大量的噪聲數據,那麼這個系統也是沒有任何意義的,因爲垃圾進,垃圾出garbage in, garbage out),系統根本就不可能爲決策分析系統提供任何支持。爲了清除噪聲數據,必須在數據庫系統中進行數據清洗。目前有不少數據清洗研究和ETL研究,但是如何在ETL過程中進行有效的數據清洗並使這個過程可視化,此方面研究不多。本文主要從兩個方面闡述ETL和數據清洗的實現過程:ETL的處理方式和數據清洗的實現方法。
(1)       ETL的處理方式
本文所採用的ETL方法是數據庫段區域中的ETL處理方式,它不使用外部引擎而是使用數據庫作爲唯一的控制點。由於源系統SQLserver2000是關係數據庫,它的段表也是典型的關係型表。成功地將外部未修改數據載入數據庫後,再在數據庫內部進行轉換。數據庫段區域中的ETL處理方式執行的步驟是提取、裝載、轉換,即通常所說的ELT。這種方式的優點是爲抽取出的數據首先提供一個緩衝以便於進行復雜的轉換,減輕了ETL進程的複雜度。
(2)       ETL過程中實現數據清洗的實現方法
首先,在理解源數據的基礎上實現數據表屬性一致化。爲解決源數據的同義異名和同名異義的問題,可通過元數據管理子系統,在理解源數據的同時,對不同表的屬性名根據其含義重新定義其在數據挖掘庫中的名字,並以轉換規則的形式存放在元數據庫中,在數據集成的時候,系統自動根據這些轉換規則將源數據中的字段名轉換成新定義的字段名,從而實現數據挖掘庫中的同名同義。
其次,通過數據縮減,大幅度縮小數據量。由於源數據量很大,處理起來非常耗時,所以可以優先進行數據縮減,以提高後續數據處理分析效率。
最後,通過預先設定數據處理的可視化功能節點,達到可視化的進行數據清洗和數據轉換的目的。針對縮減並集成後的數據,通過組合預處理子系統提供各種數據處理功能節點,能夠以可視化的方式快速有效完成數據清洗和數據轉換過程。

    現在是一個Google的時代,而對於開發者,開源已成爲最重要的參考書。對於某課題,不管你是深入研究還是初窺門徑。估且google一把,勾一勾同行的成就,你必會獲益良多。
    說到ETL開源項目,Kettle當屬翹首,項目名稱很有意思,水壺。按項目負責人Matt的說法:把各種數據放到一個壺裏,然後呢,以一種你希望的格式流出。呵呵,外國人都很有聯想力。
    看了提供的文檔,然後對發佈程序的簡單試用後,可以很清楚得看到Kettle的四大塊:
Chef——工作(job)設計工具 (GUI方式)
Kitchen——工作(job)執行器 (命令行方式)
Spoon——轉換(transform)設計工具 (GUI方式)
Span——轉換(trasform)執行器 (命令行方式)
2.1.Chef——工作(job)設計器
這是一個GUI工具,操作方式主要通過拖拖拉拉,勿庸多言,一看就會。
何謂工作?多個作業項,按特定的工作流串聯起來,開成一項工作。正如:我的工作是軟件開發。我的作業項是:設計、編碼、測試!先設計,如果成功,則編碼,否則繼續設計,編碼完成則開始設計,周而復始,作業完成。
2.1.1.    Chef中的作業項包括:
轉換:指定更細的轉換任務,通過Spoon生成。通過Field來輸入參數;
SQLsql語句執行;
FTP:下載ftp文件;
郵件:發送郵件;
檢查表是否存在
檢查文件是否存在
執行shell腳本:如dos命令。
批處理(注意:windows批處理不能有輸出到控制檯)
Job:作爲嵌套作業使用。
JavaScript執行:這個比較有意思,我看了一下源碼,如果你有自已的Script引擎,可以很方便的替換成自定義Script,來擴充其功能;
SFTP:安全的Ftp協議傳輸;
HTTP方式的上/下傳
如上文所述,工作流是作業項的連接方式。分爲三種:無條件,成功,失敗,爲了方便工作流使用,KETTLE提供了幾個輔助結點單元(也可將其作爲簡單的作業項)
Start單元:任務必須由此開始。設計作業時,以此爲起點。
OK單元:可以編制做爲中間任務單元,且進行腳本編制,用來控制流程。
ERROR單元:用途同上。
DUMMY單元:什麼都不做,主要是用來支持多分支的情況,文檔中有例子。
支持XML存儲,或存儲到指定數據庫中。
一些默認的配置(如數據庫存儲位置……),在系統的用戶目錄下,單獨建立了一個.Kettle目錄,用來保存用戶的這些設置。
可查看執行日誌。
2.2.Kitchen——作業執行器
是一個作業執行引擎,用來執行作業。這是一個命令行執行工具,沒啥可講的,就把它的參數說明列一下。
    -rep      : Repository name   任務包所在存儲名
    -user     : Repository username   執行人
    -pass     : Repository password   執行人密碼
    -job      : The name of the job to launch 任務包名稱
    -dir      : The directory (don't forget the leading / or /)
    -file     : The filename (Job XML) to launch
    -level    : The logging level (Basic, Detailed, Debug, Rowlevel, Error, Nothing) 指定日誌級別
    -log      : The logging file to write to 指定日誌文件
    -listdir : List the directories in the repository 列出指定存儲中的目錄結構。
    -listjobs : List the jobs in the specified directory 列出指定目錄下的所有任務
    -listrep : List the defined repositories 列出所有的存儲
    -norep    : Don't log into the repository 不寫日誌
    嗯,居然不支持調度。看了一下文檔,建議使用操作系統提供的調度器來實現調度,比如:Windows可以使用它的任務計劃工具。
2.3.Spoon——轉換過程設計器
    GUI工作,用來設計數據轉換過程,創建的轉換可以由Pan來執行,也可以被Chef所包含,作爲作業中的一個作業項。
    下面簡單列舉一下所有的轉換過程。(簡單描述,詳細的可見Spoon文檔)
l         Text file input:文本文件輸入
可以支持多文件合併,有不少參數,基本一看參數名就能明白其意圖。
l         Table input:數據表輸入
實際上是視圖方式輸入,因爲輸入的是sql語句。當然,需要指定數據源(數據源的定製方式在後面講一下)
l         Get system info:取系統信息
就是取一些固定的系統環境值,如本月最後一天的時間,本機的IP地址之類。
l         Generate Rows:生成多行。
這個需要匹配使用,主要用於生成多行的數據輸入,比如配合Add sequence可以生成一個指定序號的數據列。
l         XBase Input
l         Excel Input
l         XML Input
這三個沒啥可講的,看看參數就明瞭。
2.3.2.    Output-Steps: 輸出步聚
l         Text file output:文本文件輸出。這個用來作測試蠻好,呵呵。很方便的看到轉換的輸出。
l         Table output:輸出到目的表。
l         Insert/Update:目的表和輸入數據行進行比較,然後有選擇的執行增加,更新操作。
l         Update:同上,只是不支持增加操作。
l         XML Output
2.3.3.    Look-up:查找操作
l         Data Base
l         Stream
l         Procedure
l         Database join
l         Select values
對輸入的行記錄數據的字段進行更改 (更改數據類型,更改字段名或刪除數據類型變更時,數據的轉換有固定規則,可簡單定製參數。可用來進行數據表的改裝。
l         Filter rows
 對輸入的行記錄進行指定複雜條件的過濾。用途可擴充sql語句現有的過濾功能。但現有提供邏輯功能超出標準sql的不多。
l         Sort rows
對指定的列以升序或降序排序,當排序的行數超過5000時需要臨時表。
l         Add sequence
爲數據流增加一個序列,這個配合其它Step(Generate rows, rows join),可以生成序列表,如日期維度表(年、月、日)
l         Dummy
不做任何處理,主要用來作爲分支節點。
l         Join Rows
對所有輸入流做笛卡兒乘積。
l         Aggregate
聚合,分組處理
l         Group by
分組,用途可擴充sql語句現有的分組,聚合函數。但我想可能會有其它方式的sql語句能實現。
l         Java Script value
使用mozillarhino作爲腳本語言,並提供了很多函數,用戶可以在腳本中使用這些函數。
l         Row Normaliser
該步驟可以從透視表中還原數據到事實表,通過指定維度字段及其分類值,度量字段,最終還原出事實表數據。
l         Unique rows
去掉輸入流中的重複行,在使用該節點前要先排序,否則只能刪除連續的重複行。 
l         Calculator
提供了一組函數對列值進行運算,用該方式比用戶自定義JAVA SCRIPT腳本速度更快。
l         Merge Rows
用於比較兩組輸入數據,一般用於更新後的數據重新導入到數據倉庫中。
l         Add constants
增加常量值。
l         Row denormaliser
Normaliser過程相反。
l         Row flattener
表扁平化處理,指定需處理的字段和扃平化後的新字段,將其它字段做爲組合Key進行扃平化處理。
l         SPLIT FIELDS
按指定分隔符拆分字段
l         EXECUTE SQL SCRIPT
執行SQL語句
l         CUBE INPUT
l         CUBE OUTPUT
l         存儲方式:Chef相同。
l         數據源(Connection);見後。
l         Hopssetp連接起來,形成Hops
l         Plugin step types等節點:這個沒仔細看,不知如何製作Plugin step
l         LogView:可查看執行日誌。
2.4.Pan——轉換的執行工具
命令行執行方式,可以執行由Spoon生成的轉換任務。同樣,不支持調度。參數與Kitchen類似,可參見Pan的文檔。
Connection
可以配置多個數據源,在Job或是Trans中使用,這意味着可以實現跨數據庫的任務。支持大多數市面上流行的數據庫。
2.6.個人感覺:(本人不成熟的看法)
1、 轉換功能全,使用簡潔。作業項豐富,流程合理。但缺少調度。
2、 java代碼,支持的數據源範圍廣,所以,跨平臺性較好。
3、 從實際項目的角度看,和其它開源項目類似,主要還是程序員的思維,缺少與實際應用項目(專業領域)的更多接軌,當然,項目實施者的專注點可能在於一個平臺框架,而非實際應用(實際應用需要二次開發)
4、 看過了大多數源碼,發現源碼的可重用性不是太好(缺少大粒度封裝),有些關鍵部分好像有Bug。比如:個別class過於臃腫,線程實現的同步有問題。
5、 提供的工具有些小錯,如參數的容錯處理。
做數據倉庫系統,ETL是關鍵的一環。說大了,ETL是數據整合解決方案,說小了,就是倒數據的工具。回憶一下工作這麼些年來,處理數據遷移、轉換的工作倒還真的不少。但是那些工作基本上是一次性工作或者很小數據量,使用access、 DTS或是自己編個小程序搞定。可是在數據倉庫系統中,ETL上升到了一定的理論高度,和原來小打小鬧的工具使用不同了。究竟什麼不同,從名字上就可以看到,人家已經將倒數據的過程分成3個步驟,E、T、L分別代表抽取、轉換和裝載。
其實ETL過程就是數據流動的過程,從不同的數據源流向不同的目標數據。但在數據倉庫中,ETL有幾個特點,一是數據同步,它不是一次性倒完數據就拉到,它是經常性的活動,按照固定週期運行的,甚至現在還有人提出了實時ETL的概念。二是數據量,一般都是巨大的,值得你將數據流動的過程拆分成E、T和L。
現在有很多成熟的工具提供ETL功能,例如datastage、powermart 等,且不說他們的好壞。從應用角度來說,ETL的過程其實不是非常複雜,這些工具給數據倉庫工程帶來和很大的便利性,特別是開發的便利和維護的便利。但另一方面,開發人員容易迷失在這些工具中。舉個例子,VB是一種非常簡單的語言並且也是非常易用的編程工具,上手特別快,但是真正VB的高手有多少?微軟設計的產品通常有個原則是“將使用者當作傻瓜”,在這個原則下,微軟的東西確實非常好用,但是對於開發者,如果你自己也將自己當作傻瓜,那就真的傻了。 ETL工具也是一樣,這些工具爲我們提供圖形化界面,讓我們將主要的精力放在規則上,以期提高開發效率。從使用效果來說,確實使用這些工具能夠非常快速地構建一個job來處理某個數據,不過從整體來看,並不見得他的整體效率會高多少。問題主要不是出在工具上,而是在設計、開發人員上。他們迷失在工具中,沒有去探求ETL的本質。
可以說這些工具應用了這麼長時間,在這麼多項目、環境中應用,它必然有它成功之處,它必定體現了ETL的本質。如果我們不透過表面這些工具的簡單使用去看它背後蘊涵的思想,最終我們作出來的東西也就是一個個獨立的job,將他們整合起來仍然有巨大的工作量。大家都知道“理論與實踐相結合”,如果在一個領域有所超越,必須要在理論水平上達到一定的高度

4.1.ETL 特點
ETL的過程就是數據流動的過程,從不同異構數據源流向統一的目標數據。其間,數據的抽取、清洗、轉換和裝載形成串行或並行的過程。ETL的核心還是在於T這個過程,也就是轉換,而抽取和裝載一般可以作爲轉換的輸入和輸出,或者,它們作爲一個單獨的部件,其複雜度沒有轉換部件高。和OLTP系統中不同,那裏充滿這單條記錄的insert、update和select等操作,ETL過程一般都是批量操作,例如它的裝載多采用批量裝載工具,一般都是DBMS系統自身附帶的工具,例如Oracle SQLLoader和DB2的autoloader等。
ETL本身有一些特點,在一些工具中都有體現,下面以datastage和powermart舉例來說。
1、靜態的ETL單元和動態的ETL單元實例;一次轉換指明瞭某種格式的數據如何格式化成另一種格式的數據,對於數據源的物理形式在設計時可以不用指定,它可以在運行時,當這個ETL單元創建一個實例時才指定。對於靜態和動態的ETL單元,Datastage沒有嚴格區分,它的一個Job就是實現這個功能,在早期版本,一個Job同時不能運行兩次,所以一個Job相當於一個實例,在後期版本,它支持multiple instances,而且還不是默認選項。Powermart中將這兩個概念加以區分,靜態的叫做Mapping,動態運行時叫做Session。
2、ETL元數據;元數據是描述數據的數據,他的含義非常廣泛,這裏僅指ETL的元數據。主要包括每次轉換前後的數據結構和轉換的規則。ETL元數據還包括形式參數的管理,形式參數的ETL單元定義的參數,相對還有實參,它是運行時指定的參數,實參不在元數據管理範圍之內。
3、數據流程的控制;要有可視化的流程編輯工具,提供流程定義和流程監控功能。流程調度的最小單位是ETL單元實例,ETL單元是不能在細分的ETL過程,當然這由開發者來控制,例如可以將抽取、轉換放在一個ETL單元中,那樣這個抽取和轉換隻能同時運行,而如果將他們分作兩個單元,可以分別運行,這有利於錯誤恢復操作。當然,ETL單元究竟應該細分到什麼程度應該依據具體應用來看,目前還沒有找到很好的細分策略。比如,我們可以規定將裝載一個表的功能作爲一個ETL單元,但是不可否認,這樣的ETL單元之間會有很多共同的操作,例如兩個單元共用一個Hash表,要將這個Hash表裝入內存兩次。
4、轉換規則的定義方法;提供函數集提供常用規則方法,提供規則定義語言描述規則。
5、對數據的快速索引;一般都是利用Hash技術,將參照關係表提前裝入內存,在轉換時查找這個hash表。Datastage中有Hash文件技術,Powermart也有類似的Lookup功能。
4.2.ETL 類型
昨在IT-Director上閱讀一篇報告,關於ETL產品分類的。一般來說,我們眼中的ETL工具都是價格昂貴,能夠處理海量數據的傢伙,但是這是其中的一種。它可以分成4種,針對不同的需求,主要是從轉換規則的複雜度和數據量大小來看。它們包括:
1、交互式運行環境,你可以指定數據源、目標數據,指定規則,立馬ETL。這種交互式的操作無疑非常方便,但是隻能適合小數據量和複雜度不高的ETL過程,因爲一旦規則複雜了,可能需要語言級的描述,不能簡簡單單拖拖拽拽就可以的。還有數據量的問題,這種交互式必然建立在解釋型語言基礎上,另外他的靈活性必然要犧牲一定的性能爲代價。所以如果要處理海量數據的話,每次讀取一條記錄,每次對規則進行解釋執行,每次在寫入一條記錄,這對性能影響是非常大的。
2、專門編碼型的,它提供了一個基於某種語言的程序框架,你可以不必將編程精力放在一些周邊的功能上,例如讀文件功能、寫數據庫的功能,而將精力主要放在規則的實現上面。這種近似手工代碼的性能肯定是沒話說,除非你的編程技巧不過關(這也是不可忽視的因素之一)。對於處理大數據量,處理複雜轉換邏輯,這種方式的ETL實現是非常直觀的。
3、代碼生成器型的,它就像是一個ETL代碼生成器,提供簡單的圖形化界面操作,讓你拖拖拽拽將轉換規則都設定好,其實他的後臺都是生成基於某種語言的程序,要運行這個ETL過程,必須要編譯才行。Datastage就是類似這樣的產品,設計好的job必須要編譯,這避免了每次轉換的解釋執行,但是不知道它生成的中間語言是什麼。以前我設計的ETL工具大挪移其實也是歸屬於這一類,它提供了界面讓用戶編寫規則,最後生成C++語言,編譯後即可運行。這類工具的特點就是要在界面上下狠功夫,必須讓用戶輕鬆定義一個ETL過程,提供豐富的插件來完成讀、寫和轉換函數。大挪移在這方面就太弱了,規則必須手寫,而且要寫成標準c++語法,這未免還是有點難爲最終用戶了,還不如做成一個專業編碼型的產品呢。另外一點,這類工具必須提供面向專家應用的功能,因爲它不可能考慮到所有的轉換規則和所有的讀寫,一方面提供插件接口來讓第三方編寫特定的插件,另一方面還有提供特定語言來實現高級功能。例如Datastage提供一種類Basic的語言,不過他的Job的腳本化實現好像就做的不太好,只能手工繪製job,而不能編程實現Job。
4、最後還有一種類型叫做數據集線器,顧名思義,他就是像Hub一樣地工作。將這種類型分出來和上面幾種分類在標準上有所差異,上面三種更多指ETL實現的方法,此類主要從數據處理角度。目前有一些產品屬於EAI(Enterprise Application Integration),它的數據集成主要是一種準實時性。所以這類產品就像Hub一樣,不斷接收各種異構數據源來的數據,經過處理,在實施發送到不同的目標數據中去。
雖然,這些類看似各又千秋,特別在BI項目中,面對海量數據的ETL時,中間兩種的選擇就開始了,在選擇過程中,必須要考慮到開發效率、維護方面、性能、學習曲線、人員技能等各方面因素,當然還有最重要也是最現實的因素就是客戶的意象。
4.3.ETL 中的轉換-Transication
ETL探求之一中提到,ETL過程最複雜的部分就是T,這個轉換過程,T過程究竟有哪些類型呢?
從對數據源的整個宏觀處理分,看看一個ETL過程的輸入輸出,可以分成下面幾類:
1、大小交,這種處理在數據清洗過程是常見了,例如從數據源到ODS階段,如果數據倉庫採用維度建模,而且維度基本採用代理鍵的話,必然存在代碼到此鍵值的轉換。如果用SQL實現,必然需要將一個大表和一堆小表都Join起來,當然如果使用ETL工具的話,一般都是先將小表讀入內存中再處理。這種情況,輸出數據的粒度和大表一樣。
2、大大交,大表和大表之間關聯也是一個重要的課題,當然其中要有一個主表,在邏輯上,應當是主表Left Join輔表。大表之間的關聯存在最大的問題就是性能和穩定性,對於海量數據來說,必須有優化的方法來處理他們的關聯,另外,對於大數據的處理無疑會佔用太多的系統資源,出錯的機率非常大,如何做到有效錯誤恢復也是個問題。對於這種情況,我們建議還是儘量將大表拆分成適度的稍小一點的表,形成大小交的類型。這類情況的輸出數據粒度和主表一樣。
3、站着進來,躺着出去。事務系統中爲了提高系統靈活性和擴展性,很多信息放在代碼表中維護,所以它的“事實表”就是一種窄表,而在數據倉庫中,通常要進行寬化,從行變成列,所以稱這種處理情況叫做“站着進來,躺着出去”。大家對 Decode肯定不陌生,這是進行寬表化常見的手段之一。窄表變寬表的過程主要體現在對窄表中那個代碼字段的操作。這種情況,窄表是輸入,寬表是輸出,寬表的粒度必定要比窄表粗一些,就粗在那個代碼字段上。
4、聚集。數據倉庫中重要的任務就是沉澱數據,聚集是必不可少的操作,它是粗化數據粒度的過程。聚集本身其實很簡單,就是類似SQL中Group by的操作,選取特定字段(維度),對度量字段再使用某種聚集函數。但是對於大數據量情況下,聚集算法的優化仍是探究的一個課題。例如是直接使用SQL的 Group by,還是先排序,在處理。
從數據的轉換的微觀細節分,可以分成下面的幾個基本類型,當然還有一些複雜的組合情況,例如先運算,在參照轉換的規則,這種基於基本類型組合的情況就不在此列了。ETL的規則是依賴目標數據的,目標數據有多少字段,就有多少條規則。
1、直接映射,原來是什麼就是什麼,原封不動照搬過來,對這樣的規則,如果數據源字段和目標字段長度或精度不符,需要特別注意看是否真的可以直接映射還是需要做一些簡單運算;
2、字段運算,數據源的一個或多個字段進行數學運算得到的目標字段,這種規則一般對數值型字段而言;
3、參照轉換,在轉換中通常要用數據源的一個或多個字段作爲Key。
4.4.ETL中數據質量
“不要絕對的數據準確,但要知道爲什麼不準確。”這是我們在構建BI系統是對數據準確性的要求。確實,對絕對的數據準確誰也沒有把握,不僅是系統集成商,包括客戶也是無法確定。準確的東西需要一個標準,但首先要保證這個標準是準確的,至少現在還沒有這樣一個標準。客戶會提出一個相對標準,例如將你的OLAP數據結果和報表結果對比。雖然這是一種不太公平的比較,你也只好認了吧。
首先在數據源那裏,已經很難保證數據質量了,這一點也是事實。在這一層有哪些可能原因導致數據質量問題?可以分爲下面幾類:
1、數據格式錯誤,例如缺失數據、數據值超出範圍或是數據格式非法等。要知道對於同樣處理大數據量的數據源系統,他們通常會捨棄一些數據庫自身的檢查機制,例如字段約束等。他們儘可能將數據檢查在入庫前保證,但是這一點是很難確保的。這類情況諸如身份證號碼、手機號、非日期類型的日期字段等。
2、數據一致性,同樣,數據源系統爲了性能的考慮,會在一定程度上舍棄外鍵約束,這通常會導致數據不一致。例如在帳務表中會出現一個用戶表中沒有的用戶ID,在例如有些代碼在代碼表中找不到等。
3、業務邏輯的合理性,這一點很難說對與錯。通常,數據源系統的設計並不是非常嚴謹,例如讓用戶開戶日期晚於用戶銷戶日期都是有可能發生的,一個用戶表中存在多個用戶ID也是有可能發生的。對這種情況,有什麼辦法嗎?
構建一個BI系統,要做到完全理解數據源系統根本就是不可能的。特別是數據源系統在交付後,有更多維護人員的即興發揮,那更是要花大量的時間去尋找原因。以前曾經爭辯過設計人員對規則描述的問題,有人提出要在ETL開始之前務必將所有的規則弄得一清二楚。我並不同意這樣的意見,倒是認爲在ETL過程要有處理這些質量有問題數據的保證。一定要正面這些髒數據,是丟棄還是處理,無法逃避。如果沒有質量保證,那麼在這個過程中,錯誤會逐漸放大,拋開數據源質量問題,我們再來看看ETL過程中哪些因素對數據準確性產生重大影響。
1、規則描述錯誤。上面提到對設計人員對數據源系統理解的不充分,導致規則理解錯誤,這是一方面。另一方面,是規則的描述,如果無二義性地描述規則也是要探求的一個課題。規則是依附於目標字段的,在探求之三中,提到規則的分類。但是規則總不能總是用文字描述,必須有嚴格的數學表達方式。我甚至想過,如果設計人員能夠使用某種規則語言來描述,那麼我們的ETL單元就可以自動生成、同步,省去很多手工操作了。
2、ETL開發錯誤。即時規則很明確,ETL開發的過程中也會發生一些錯誤,例如邏輯錯誤、書寫錯誤等。例如對於一個分段值,開區間閉區間是需要指定的,但是常常開發人員沒注意,一個大於等於號寫成大於號就導致數據錯誤。
3、人爲處理錯誤。在整體ETL流程沒有完成之前,爲了圖省事,通常會手工運行ETL過程,這其中一個重大的問題就是你不會按照正常流程去運行了,而是按照自己的理解去運行,發生的錯誤可能是誤刪了數據、重複裝載數據等。
4.5.ETL數據質量保證
上回提到ETL數據質量問題,這是無法根治的,只能採取特定的手段去儘量避免,而且必須要定義出度量方法來衡量數據的質量是好還是壞。對於數據源的質量,客戶對此應該更加關心,如果在這個源頭不能保證比較乾淨的數據,那麼後面的分析功能的可信度也都成問題。數據源系統也在不斷進化過程中,客戶的操作也在逐漸規範中,BI系統也同樣如此。本文探討一下對數據源質量和ETL處理質量的應對方法。
如何應對數據源的質量問題?記得在onteldatastage列表中也討論過一個話題-"-1的處理",在數據倉庫模型維表中,通常有一條-1記錄,表示“未知”,這個未知含義可廣了,任何可能出錯的數據,NULL數據甚至是規則沒有涵蓋到的數據,都轉成-1。這是一種處理髒數據的方法,但這也是一種掩蓋事實的方法。就好像寫一個函數FileOpen(filename),返回一個錯誤碼,當然,你可以只返回一種錯誤碼,如-1,但這是一種不好的設計,對於調用者來說,他需要依據這個錯誤碼進行某些判斷,例如是文件不存在,還是讀取權限不夠,都有相應的處理邏輯。數據倉庫中也是一樣,所以,建議將不同的數據質量類型處理結果分別轉換成不同的值,譬如,在轉換後,-1表示參照不上,-2表示NULL數據等。不過這僅僅對付了上回提到的第一類錯誤,數據格式錯誤。對於數據一致性和業務邏輯合理性問題,這仍有待探求。但這裏有一個原則就是“必須在數據倉庫中反應數據源的質量”。
對於ETL過程中產生的質量問題,必須有保障手段。從以往的經驗看,沒有保障手段給實施人員帶來麻煩重重。實施人員對於反覆裝載數據一定不會陌生,甚至是最後數據留到最後的Cube,才發現了第一步ETL其實已經錯了。這個保障手段就是數據驗證機制,當然,它的目的是能夠在ETL過程中監控數據質量,產生報警。這個模塊要將實施人員當作是最終用戶,可以說他們是數據驗證機制的直接收益者。
首先,必須有一個對質量的度量方法,什麼是高質什麼是低質,不能靠感官感覺,但這卻是在沒有度量方法條件下通常的做法。那經營分析系統來說,聯通總部曾提出測試規範,這其實就是一種度量方法,例如指標的誤差範圍不能高於5%等,對系統本身來說其實必須要有這樣的度量方法,先不要說這個度量方法是否科學。對於ETL數據處理質量,他的度量方法應該比聯通總部測試規範定義的方法更要嚴格,因爲他更多將BI系統看作一個黑盒子,從數據源到展現的數據誤差允許一定的誤差。而ETL數據處理質量度量是一種白盒的度量,要注重每一步過程。因此理論上,要求輸入輸出的指標應該完全一致。但是我們必須正面完全一致只是理想,對於有誤差的數據,必須找到原因。
在質量度量方法的前提下,就可以建立一個數據驗證框架。此框架依據總量、分量數據稽覈方法,該方法在高的《數據倉庫中的數據稽核技術》一文中已經指出。作爲補充,下面提出幾點功能上的建議:
1、提供前端。將開發實施人員當作用戶,同樣也要爲之提供友好的用戶界面。《稽核技術》一文中指出測試報告的形式,這種形式還是要依賴人爲判斷,在一堆數據中去找規律。到不如用OLAP的方式提供界面,不光是加上測試統計出來的指標結果,並且配合度量方法的計算。例如誤差率,對於誤差率爲大於0的指標,就要好好查一下原因了。
2、提供框架。數據驗證不是一次性工作,而是每次ETL過程中都必須做的。因此,必須有一個框架,自動化驗證過程,並提供擴展手段,讓實施人員能夠增加驗證範圍。有了這樣一個框架,其實它起到規範化操作的作用,開發實施人員可以將主要精力放在驗證腳本的編寫上,而不必過多關注驗證如何融合到流程中,如何展現等工作。爲此,要設計一套表,類似於DM表,每次驗證結果數據都記錄其中,並且自動觸發多維分析的數據裝載、發佈等。這樣,實施人員可以在每次裝載,甚至在流程過程中就可以觀察數據的誤差率。特別是,如果數據倉庫的模型能夠統一起來,甚至數據驗證腳本都可以確定下來,剩下的就是規範流程了。
3、規範流程。上回提到有一種ETL數據質量問題是由於人工處理導致的,其中最主要原因還是流程不規範。開發實施人員運行單獨一個ETL單元是很方便的,雖然以前曾建議一個ETL單元必須是“可重入”的,這能夠解決誤刪數據,重複裝載數據問題。但要記住數據驗證也是在流程當中,要讓數據驗證能夠日常運作,就不要讓實施者感覺到他的存在。總的來說,規範流程是提高實施效率的關鍵工作,這也是以後要繼續探求的。
對於元數據(Metadata)的定義到目前爲止沒有什麼特別精彩的,這個概念非常廣,一般都是這樣定義,“元數據是描述數據的數據(Data about Data)”,這造成一種遞歸定義,就像問小強住在哪裏,答,在旺財隔壁。按照這樣的定義,元數據所描述的數據是什麼呢?還是元數據。這樣就可能有元元元...元數據。我還聽說過一種對元數據,如果說數據是一抽屜檔案,那麼元數據就是分類標籤。那它和索引有什麼區別?
元數據體現是一種抽象,哲學家從古至今都在抽象這個世界,力圖找到世界的本質。抽象不是一層關係,它是一種逐步由具體到一般的過程。例如我->男人->人->哺乳動物->生物這就是一個抽象過程,你要是在軟件業混會發現這個例子很常見,面向對象方法就是這樣一種抽象過程。它對世界中的事物、過程進行抽象,使用面向對象方法,構建一套對象模型。同樣在面向對象方法中,類是對象的抽象,接口又是對類的抽象。因此,我認爲可以將“元”和“抽象”換一下,叫抽象數據是不是好理解一些。
常聽到這樣的話,“xx領導的講話高屋建瓴,給我們後面的工作指引的清晰的方向”,這個成語“高屋建瓴”,站在10樓往下到水,居高臨下,能砸死人,這是指站在一定的高度看待事物,這個一定的高度就是指他有夠“元”。在設計模式中,強調要對接口編程,就是說你不要處理這類對象和那類對象的交互,而要處理這個接口和那個接口的交互,先別管他們內部是怎麼幹的。
元數據存在的意義也在於此,雖然上面說了一通都撤到哲學上去,但這個詞必須還是要結合軟件設計中看,我不知道在別的領域是不是存在Metadata這樣的叫法,雖然我相信別的領域必然有類似的東東。元數據的存在就是要做到在更高抽象一層設計軟件。這肯定有好處,什麼靈活性啊,擴展性啊,可維護性啊,都能得到提高,而且架構清晰,只是彎彎太多,要是從下往上看,太複雜了。很早以前,我曾看過 backorifice的代碼,我靠,一個簡單的功能,從這個類轉到父類,又轉到父類,很不理解,爲什麼一個簡單的功能不在一個類的方法中實現就拉到了呢?現在想想,還真不能這樣,這雖然使代碼容易看懂了,但是結構確實混亂的,那他只能幹現在的事,如果有什麼功能擴展,這些代碼就廢了。
我從98年剛工作時就開始接觸元數據的概念,當時叫做元數據驅動的系統架構,後來在 QiDSS中也用到這個概念構建QiNavigator,但是現在覺得元數據也沒啥,不就是建一堆表描述界面的元素,再利用這些數據自動生成界面嗎。到了數據倉庫系統中,這個概念更強了,是數據倉庫中一個重要的部分。但是至今,我還是認爲這個概念過於玄乎,看不到實際的東西,市面上有一些元數據管理的東西,但是從應用情況就得知,用的不多。之所以玄乎,就是因爲抽象層次沒有分清楚,關鍵就是對於元數據的分類(這種分類就是一種抽象過程)和元數據的使用。你可以將元數據抽象成0和1,但是那樣對你的業務有用嗎?必須還得抽象到適合的程度,最後問題還是“度”。
數據倉庫系統的元數據作用如何?還不就是使系統自動運轉,易於管理嗎?要做到這一步,可沒必要將系統抽象到太極、兩儀、八卦之類的,業界也曾定義過一些元數據規範,向CWM、XMI等等,可以借鑑,不過俺對此也是不精通的說,以後再說。


文章二:Kattle API 實戰
前言:

爲什麼要用Kettle和KETTLE JAVA API?
Kettle是什麼?kettle:是一個開源ETL工具。kettle提供了基於java的圖形化界面,使用很方便,kettle的ETL工具集合也比較多,常用的ETL工具都包含了。

爲什麼使用KETTLE JAVA API:就像kettle文檔所說:KETTLE JAVA API : Program your own Kettle transformation,kettle提供了基於 JAVA的腳步編寫功能,可以靈活地自定義ETL過程,使自行定製、批量處理等成爲可能,這纔是一個程序員需要做的工作,而不僅是象使用word一樣操作 kettle用戶界面。

KETTLE JAVA API 實戰操作記錄:

、         搭建環境 :到http://www.kettle.be網站下載kettle的源碼包,加壓縮,例如解壓縮到d:/kettle目錄

、         打開eclipse,新建一個項目,要使用jdk1.5.0,因爲kettle的要使用System.getenv(),只有在jdk1.5.0才被支持。提起getenv(),好像有一段幾起幾落的記錄,曾一度被拋棄,現在又被jdk1.5支持了.

、         建一個class : TransBuilder.java,可以把d:/kettle/ extra/TransBuilder.java的內容原樣拷貝到你的TransBuilder.java裏。

、         根據需要編輯源碼。並需要對原程序進行如下修改,在頭部增加:

import org.eclipse.swt.dnd.Transfer;

//這個包被遺漏了,原始位置kettle根目錄/libswt/win32/swt.jar

//add by chq(www.chq.name) on  2006.07.20

(後來發現,不必加這個引用,因爲編譯時不需要)

、         編譯準備,在eclipse中增加jar包,主要包括(主要依據extra/TransBuilder.bat):

/lib/kettle.jar
/libext/CacheDB.jar
/libext/SQLBaseJDBC.jar
/libext/activation.jar
/libext/db2jcc.jar
/libext/db2jcc_license_c.jar
/libext/edtftpj-1.4.5.jar
/libext/firebirdsql-full.jar
/libext/firebirdsql.jar
/libext/gis-shape.jar
/libext/hsqldb.jar
/libext/ifxjdbc.jar
/libext/javadbf.jar
/libext/jconn2.jar
/libext/js.jar
/libext/jt400.jar
/libext/jtds-1.1.jar
/libext/jxl.jar
/libext/ktable.jar
/libext/log4j-1.2.8.jar
/libext/mail.jar
/libext/mysql-connector-java-3.1.7-bin.jar
/libext/ojdbc14.jar
/libext/orai18n.jar
/libext/pg74.215.jdbc3.jar
/libext/edbc.jar

(注意 :下面這個包被遺漏了,要加上。原始位置kettle根目錄/libswt/win32/swt.jar)
/libswt/win32/swt.jar 

、         編譯成功後,準備運行

爲使程序不必登陸就可以運行,需要設置環境署文件:kettle.properties,位置在用戶目錄裏,一般在 /Documents and Settings/用戶/.kettle/,主要內容如下:

KETTLE_REPOSITORY=kettle@m80

KETTLE_USER=admin

KETTLE_PASSWORD=passwd

、         好了,現在可以運行一下了,看看數據是不是已經拷貝到目標表了。

以下是運行時的控制檯信息輸出:



下面是自動生成的Transformation :



以下爲修改後的程序源碼:


--------------------------------------------------------------------------------

  1. package name.chq.test;

  2.  

  3. import java.io.DataOutputStream;

  4. import java.io.File;

  5. import java.io.FileOutputStream;

  6.  

  7. import be.ibridge.kettle.core.Const;

  8. import be.ibridge.kettle.core.LogWriter;

  9. import be.ibridge.kettle.core.NotePadMeta;

  10. import be.ibridge.kettle.core.database.Database;

  11. import be.ibridge.kettle.core.database.DatabaseMeta;

  12. import be.ibridge.kettle.core.exception.KettleException;

  13. import be.ibridge.kettle.core.util.EnvUtil;

  14. import be.ibridge.kettle.trans.StepLoader;

  15. import be.ibridge.kettle.trans.Trans;

  16. import be.ibridge.kettle.trans.TransHopMeta;

  17. import be.ibridge.kettle.trans.TransMeta;

  18. import be.ibridge.kettle.trans.step.StepMeta;

  19. import be.ibridge.kettle.trans.step.StepMetaInterface;

  20. import be.ibridge.kettle.trans.step.selectvalues.SelectValuesMeta;

  21. import be.ibridge.kettle.trans.step.tableinput.TableInputMeta;

  22. import be.ibridge.kettle.trans.step.tableoutput.TableOutputMeta;

  23.  

  24.  

  25. //這個包被遺漏了,原始位置kettle根目錄/libswt/win32/swt.jar

  26. //add by chq([link=http://www.chq.name]www.chq.name[/link]) on  2006.07.20

  27. //import org.eclipse.swt.dnd.Transfer; 

  28.  

  29. /**

  30.  * Class created to demonstrate the creation of transformations on-the-fly.

  31.  * 

  32.  * @author Matt

  33.  * 

  34.  */

  35. public class TransBuilder

  36. {

  37.     public static final String[] databasesXML = {

  38.         "<?xml version=/"1.0/" encoding=/"UTF-8/"?>" +

  39.         "<connection>" +

  40.           "<name>target</name>" +

  41.           "<server>192.168.17.35</server>" +

  42.           "<type>ORACLE</type>" +

  43.                      "<access>Native</access>" +

  44.                      "<database>test1</database>" +

  45.                      "<port>1521</port>" +

  46.                      "<username>testuser</username>" +

  47.                      "<password>pwd</password>" +

  48.                      "<servername/>" +

  49.                      "<data_tablespace/>" +

  50.                      "<index_tablespace/>" +

  51.                      "<attributes>" +

  52.                        "<attribute><code>EXTRA_OPTION_MYSQL.defaultFetchSize</code><attribute>500</attribute></attribute>" +

  53.                        "<attribute><code>EXTRA_OPTION_MYSQL.useCursorFetch</code><attribute>true</attribute></attribute>" +

  54.                           "<attribute><code>PORT_NUMBER</code><attribute>1521</attribute></attribute>" +

  55.                             "</attributes>" +

  56.                        "</connection>" ,

  57.          

  58.         "<?xml version=/"1.0/" encoding=/"UTF-8/"?>" +

  59.                          "<connection>" +

  60.                                 "<name>source</name>" +

  61.                                 "<server>192.168.16.12</server>" +

  62.                                 "<type>ORACLE</type>" +

  63.                                 "<access>Native</access>" +

  64.                                 "<database>test2</database>" +

  65.                                 "<port>1521</port>" +

  66.                                 "<username>testuser</username>" +

  67.                                 "<password>pwd2</password>" +

  68.                                 "<servername/>" +

  69.                                 "<data_tablespace/>" +

  70.                                 "<index_tablespace/>" +

  71.                                 "<attributes>" +

  72.                                     "<attribute><code>EXTRA_OPTION_MYSQL.defaultFetchSize</code><attribute>500</attribute></attribute>" +

  73.                                     "<attribute><code>EXTRA_OPTION_MYSQL.useCursorFetch</code><attribute>true</attribute></attribute>" +

  74.                                        "<attribute><code>PORT_NUMBER</code><attribute>1521</attribute></attribute>" +

  75.                                 "</attributes>" +

  76.                          "</connection>" 

  77.     };

  78.  

  79.     /**

  80.      * Creates a new Transformation using input parameters such as the tablename to read from.

  81.      * @param transformationName The name of the transformation

  82.      * @param sourceDatabaseName The name of the database to read from

  83.      * @param sourceTableName The name of the table to read from

  84.      * @param sourceFields The field names we want to read from the source table

  85.      * @param targetDatabaseName The name of the target database

  86.      * @param targetTableName The name of the target table we want to write to

  87.      * @param targetFields The names of the fields in the target table (same number of fields as sourceFields)

  88.      * @return A new transformation

  89.      * @throws KettleException In the rare case something goes wrong

  90.      */

  91.     public static final TransMeta buildCopyTable(

  92.        String transformationName,String sourceDatabaseName, String sourceTableName, 

  93.        String[] sourceFields, String targetDatabaseName, String targetTableName, 

  94.        String[] targetFields)

  95.       throws KettleException

  96.     {

  97.         LogWriter log = LogWriter.getInstance();

  98.         EnvUtil.environmentInit();

  99.         try

  100.         {

  101.             //

  102.             // Create a new transformation...

  103.             //

  104.             TransMeta transMeta = new TransMeta();

  105.             transMeta.setName(transformationName);

  106.             

  107.             // Add the database connections

  108.  

  109.             for (int i=0;i<databasesXML.length;i++)

  110.             {

  111.                 DatabaseMeta databaseMeta = new DatabaseMeta(databasesXML[i]);

  112.                 transMeta.addDatabase(databaseMeta);

  113.             }

  114.             

  115.             DatabaseMeta sourceDBInfo = transMeta.findDatabase(sourceDatabaseName);

  116.             DatabaseMeta targetDBInfo = transMeta.findDatabase(targetDatabaseName);

  117.  

  118.             

  119.             //

  120.             // Add a note

  121.             //

  122.             String note = "Reads information from table [" + sourceTableName+ "] on database [" 

  123.                             + sourceDBInfo + "]" + Const.CR;

  124.             note += "After that, it writes the information to table [" + targetTableName + "] on database [" 

  125.                             + targetDBInfo + "]";

  126.             NotePadMeta ni = new NotePadMeta(note, 150, 10, -1, -1);

  127.             transMeta.addNote(ni);

  128.  

  129.             // 

  130.             // create the source step...

  131.             //

  132.             String fromstepname = "read from [" + sourceTableName + "]";

  133.             TableInputMeta tii = new TableInputMeta();

  134.             tii.setDatabaseMeta(sourceDBInfo);

  135.             String selectSQL = "SELECT "+Const.CR;

  136.             for (int i=0;i<sourceFields.length;i++)

  137.             {

  138.             /* modi by chq(www.chq.name): use * to replace the fields,經分析,以下語句可以處理‘*‘ */

  139.                 if (i>0) 

  140.                      selectSQL+=", "

  141.                 else selectSQL+="  ";

  142.                

  143.                 selectSQL+=sourceFields[i]+Const.CR;

  144.             }

  145.             selectSQL+="FROM "+sourceTableName;

  146.             tii.setSQL(selectSQL);

  147.  

  148.             StepLoader steploader = StepLoader.getInstance();

  149.  

  150.             String fromstepid = steploader.getStepPluginID(tii);

  151.             StepMeta fromstep = new StepMeta(log, fromstepid, fromstepname, (StepMetaInterface) tii);

  152.             fromstep.setLocation(150, 100);

  153.             fromstep.setDraw(true);

  154.             fromstep.setDescription("Reads information from table [" + sourceTableName 

  155.                                      + "] on database [" + sourceDBInfo + "]");

  156.             transMeta.addStep(fromstep);

  157.  

  158.             //

  159.             // add logic to rename fields

  160.             // Use metadata logic in SelectValues, use SelectValueInfo...

  161.             //

  162.             /* 不必改名或映射 add by chq(www.chq.name) on 2006.07.20

  163.             SelectValuesMeta svi = new SelectValuesMeta();

  164.             svi.allocate(0, 0, sourceFields.length);

  165.             for (int i = 0; i < sourceFields.length; i++)

  166.             {

  167.                 svi.getMetaName()[i] = sourceFields[i];

  168.                 svi.getMetaRename()[i] = targetFields[i];

  169.             }

  170.  

  171.             String selstepname = "Rename field names";

  172.             String selstepid = steploader.getStepPluginID(svi);

  173.             StepMeta selstep = new StepMeta(log, selstepid, selstepname, (StepMetaInterface) svi);

  174.             selstep.setLocation(350, 100);

  175.             selstep.setDraw(true);

  176.             selstep.setDescription("Rename field names");

  177.             transMeta.addStep(selstep);

  178.  

  179.             TransHopMeta shi = new TransHopMeta(fromstep, selstep);

  180.             transMeta.addTransHop(shi);

  181.             fromstep = selstep; //設定了新的起點 by chq([link=http://www.chq.name]www.chq.name[/link]) on 2006.07.20

  182.             */

  183.             // 

  184.             // Create the target step...

  185.             //

  186.             //

  187.             // Add the TableOutputMeta step...

  188.             //

  189.             String tostepname = "write to [" + targetTableName + "]";

  190.             TableOutputMeta toi = new TableOutputMeta();

  191.             toi.setDatabase(targetDBInfo);

  192.             toi.setTablename(targetTableName);

  193.             toi.setCommitSize(200);

  194.             toi.setTruncateTable(true);

  195.  

  196.             String tostepid = steploader.getStepPluginID(toi);

  197.             StepMeta tostep = new StepMeta(log, tostepid, tostepname, (StepMetaInterface) toi);

  198.             tostep.setLocation(550, 100);

  199.             tostep.setDraw(true);

  200.             tostep.setDescription("Write information to table [" + targetTableName + "] on database [" + targetDBInfo + "]");

  201.             transMeta.addStep(tostep);

  202.  

  203.             //

  204.             // Add a hop between the two steps...

  205.             //

  206.             TransHopMeta hi = new TransHopMeta(fromstep, tostep);

  207.             transMeta.addTransHop(hi);

  208.  

  209.             // OK, if we're still here: overwrite the current transformation...

  210.             return transMeta;

  211.         }

  212.         catch (Exception e)

  213.         {

  214.             throw new KettleException("An unexpected error occurred creating the new transformation", e);

  215.         }

  216.     }

  217.  

  218.     /**

  219.      * 1) create a new transformation

  220.      * 2) save the transformation as XML file

  221.      * 3) generate the SQL for the target table

  222.      * 4) Execute the transformation

  223.      * 5) drop the target table to make this program repeatable

  224.      * 

  225.      * @param args

  226.      */

  227.     public static void main(String[] args) throws Exception

  228.     {

  229.        EnvUtil.environmentInit();

  230.         // Init the logging...

  231.         LogWriter log = LogWriter.getInstance("TransBuilder.log"true, LogWriter.LOG_LEVEL_DETAILED);

  232.         

  233.         // Load the Kettle steps & plugins 

  234.         StepLoader stloader = StepLoader.getInstance();

  235.         if (!stloader.read())

  236.         {

  237.             log.logError("TransBuilder",  "Error loading Kettle steps & plugins... stopping now!");

  238.             return;

  239.         }

  240.         

  241.         // The parameters we want, optionally this can be 

  242.         String fileName = "NewTrans.xml";

  243.         String transformationName = "Test Transformation";

  244.         String sourceDatabaseName = "source";

  245.         String sourceTableName = "testuser.source_table";

  246.         String sourceFields[] = { 

  247.                "*" 

  248.                };

  249.  

  250.         String targetDatabaseName = "target";

  251.         String targetTableName = "testuser.target_table";

  252.         String targetFields[] = { 

  253.                "*"

  254.                };

  255.  

  256.         

  257.         // Generate the transformation.

  258.         TransMeta transMeta = TransBuilder.buildCopyTable(

  259.                 transformationName,

  260.                 sourceDatabaseName,

  261.                 sourceTableName,

  262.                 sourceFields,

  263.                 targetDatabaseName,

  264.                 targetTableName,

  265.                 targetFields

  266.                 );

  267.         

  268.         // Save it as a file:

  269.         String xml = transMeta.getXML();

  270.         DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(fileName)));

  271.         dos.write(xml.getBytes("UTF-8"));

  272.         dos.close();

  273.         System.out.println("Saved transformation to file: "+fileName);

  274.  

  275.         // OK, What's the SQL we need to execute to generate the target table?

  276.         String sql = transMeta.getSQLStatementsString();

  277.         

  278.         // Execute the SQL on the target table:

  279.         Database targetDatabase = new Database(transMeta.findDatabase(targetDatabaseName));

  280.         targetDatabase.connect();

  281.         targetDatabase.execStatements(sql);

  282.         

  283.         // Now execute the transformation...

  284.         Trans trans = new Trans(log, transMeta);

  285.         trans.execute(null);

  286.         trans.waitUntilFinished();

  287.         

  288.         // For testing/repeatability, we drop the target table again

  289.         /* modi by chq([link=http://www.chq.name]www.chq.name[/link]) on  2006.07.20 不必刪表

  290.         //targetDatabase.execStatement("drop table "+targetTableName);

  291.         targetDatabase.disconnect();

  292.     }
  293.  

文章三:Kattle Java API

http://wiki.pentaho.com/display/EAI/Pentaho+Data+Integration+-+Java+API+Examples

KETTLE JAVA API   http://kettle.pentaho.org/downloads/api.php

Program your own Kettle transformation

The example described below performs the following actions:

  1. create a new transformation
  2. save the transformation as XML file
  3. generate the SQL for the target table
  4. Execute the transformation
  5. drop the target table to make this program repeatable

The complete source code for the example is distributed in the distribution zip file. You can find this file in the downloads section. (Kettle version 2.1.3 or higher)

After unzipping this file, you can find the source code in the 揟ransBuilder.java� file in the 揺xtra� directory.

The Kettle Java API for Kettle is found here: Kettle Java API

// Generate the transformation.
TransMeta transMeta = TransBuilder.buildCopyTable(
transformationName,
sourceDatabaseName,
sourceTableName,
sourceFields,
targetDatabaseName,
targetTableName,
targetFields
);

// Save it as a file:
String xml = transMeta.getXML();
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(fileName)));
dos.write(xml.getBytes("UTF-8"));
dos.close();
System.out.println("Saved transformation to file: "+fileName);

// OK, What's the SQL we need to execute to generate the target table?
String sql = transMeta.getSQLStatementsString();

// Execute the SQL on the target table:
Database targetDatabase = new Database(transMeta.findDatabase(targetDatabaseName));
targetDatabase.connect();
targetDatabase.execStatements(sql);

// Now execute the transformation...
Trans trans = new Trans(log, transMeta);
trans.execute(null);
trans.waitUntilFinished();

// For testing/repeatability, we drop the target table again
targetDatabase.execStatement("drop table "+targetTableName);
targetDatabase.disconnect();

Below is the source code for the method that creates the transformation:

/**
* Creates a new Transformation using input parameters such as the tablename to read from.
* @param transformationName The name of the transformation
* @param sourceDatabaseName The name of the database to read from
* @param sourceTableName The name of the table to read from
* @param sourceFields The field names we want to read from the source table
* @param targetDatabaseName The name of the target database
* @param targetTableName The name of the target table we want to write to
* @param targetFields The names of the fields in the target table (same number of fields as sourceFields)
* @return A new transformation metadata object
* @throws KettleException In the rare case something goes wrong
*/

public static final TransMeta buildCopyTable(
String transformationName, 
String sourceDatabaseName, 
String sourceTableName, 
String[] sourceFields, 
String targetDatabaseName, 
String targetTableName, 
String[] targetFields) throws KettleException
{

LogWriter log = LogWriter.getInstance();

try
{

//
// Create a new transformation...
//
TransMeta transMeta = new TransMeta();
transMeta.setName(transformationName);

// Add the database connections
for (int i=0;i<databasesXML.length;i++)
{
DatabaseMeta databaseMeta = new DatabaseMeta(databasesXML[i]);
transMeta.addDatabase(databaseMeta);
}

DatabaseMeta sourceDBInfo = transMeta.findDatabase(sourceDatabaseName);
DatabaseMeta targetDBInfo = transMeta.findDatabase(targetDatabaseName);

//
// Add a note
//

String note = "Reads information from table [" + sourceTableName+ "] on database [" + sourceDBInfo + "]" + Const.CR;
note += "After that, it writes the information to table [" + targetTableName + "] on database [" + targetDBInfo + "]";
NotePadMeta ni = new NotePadMeta(note, 150, 10, -1, -1);
transMeta.addNote(ni);

// 
// create the source step...
//

String fromstepname = "read from [" + sourceTableName + "]";
TableInputMeta tii = new TableInputMeta();
tii.setDatabaseMeta(sourceDBInfo);
String selectSQL = "SELECT "+Const.CR;
for (int i=0;i<sourceFields.length;i++)
{
if (i>0) selectSQL+=", "; else selectSQL+=" ";
selectSQL+=sourceFields[i]+Const.CR;
}
selectSQL+="FROM "+sourceTableName;
tii.setSQL(selectSQL);

StepLoader steploader = StepLoader.getInstance();

String fromstepid = steploader.getStepPluginID(tii);
StepMeta fromstep = new StepMeta(log, fromstepid, fromstepname, (StepMetaInterface) tii);
fromstep.setLocation(150, 100);
fromstep.setDraw(true);
fromstep.setDescription("Reads information from table [" + sourceTableName + "] on database [" + sourceDBInfo + "]");
transMeta.addStep(fromstep);

//
// add logic to rename fields
// Use metadata logic in SelectValues, use SelectValueInfo...
//

SelectValuesMeta svi = new SelectValuesMeta();
svi.allocate(0, 0, sourceFields.length);
for (int i = 0; i < sourceFields.length; i++)
{

svi.getMetaName()[i] = sourceFields[i];
svi.getMetaRename()[i] = targetFields[i];

}

String selstepname = "Rename field names";
String selstepid = steploader.getStepPluginID(svi);
StepMeta selstep = new StepMeta(log, selstepid, selstepname, (StepMetaInterface) svi);
selstep.setLocation(350, 100);
selstep.setDraw(true);
selstep.setDescription("Rename field names");
transMeta.addStep(selstep);

TransHopMeta shi = new TransHopMeta(fromstep, selstep);
transMeta.addTransHop(shi);
fromstep = selstep;

// 
// Create the target step...
//

//
// Add the TableOutputMeta step...
//

String tostepname = "write to [" + targetTableName + "]";
TableOutputMeta toi = new TableOutputMeta();
toi.setDatabase(targetDBInfo);
toi.setTablename(targetTableName);
toi.setCommitSize(200);
toi.setTruncateTable(true);

String tostepid = steploader.getStepPluginID(toi);
StepMeta tostep = new StepMeta(log, tostepid, tostepname, (StepMetaInterface) toi);
tostep.setLocation(550, 100);

tostep.setDraw(true);
tostep.setDescription("Write information to table [" + targetTableName + "] on database [" + targetDBInfo + "]");
transMeta.addStep(tostep);

//
// Add a hop between the two steps...
//

TransHopMeta hi = new TransHopMeta(fromstep, tostep);
transMeta.addTransHop(hi);

// The transformation is complete, return it...
return transMeta;
}
catch (Exception e)
{

throw new KettleException("An unexpected error occurred creating the new transformation", e);

}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章