案例背景與需求介紹
之前做過一個美國的醫療保險的項目,保險提供商有大量的文件需要發送給比如像銀行,醫療協會,第三方服務商等。比如像與銀行交互的 ACH 文件,傳送給協會的 ACH Credit 等文件。這些文件格式在美國都是開放的,通用的,可以直接到相關網站下載。也就是說像銀行,協會等他們接受這種固定格式的文件,讀取數據,讀取公司編號進行業務來往或者記錄。我當時就是直接在網上搜索到一個 PDF 格式的文件說明,大概有10來頁,就是告訴你這個格式是如何定義,應該如何來處理的。
那么這種文件並非像我們通常看到的一個文件所有的行都是遵循一種格式,這種文件分為很多 Section:
- File Header/Trailer
- Batch Header/Trailer
- 還有細節的交易數據等
可以參看 - http://www.treasurysoftware.com/ach.html 或 http://www.treasurysoftware.com/ach-file-format.html
會發現第1,2,3-5,6,7 行它們的格式都不一樣,不同於我們之前的平面文件輸出,在同一種格式下批量輸出。

它們的文件格式說明就明確規定了某一行的輸出有多少列,每列應該是什么內容,寬度,格式等等都嚴格的定義了。

案例演示與講解
下面我使用 Adventure Works DW 2012 數據中的數據模擬輸出類似於這種格式的平面文件。
比如下方是我的一個文件示例,第一行是單獨的一個 Section,第二行也是,自第三行起到倒數第二行的格式是相同的,最后一行又是獨立的一個輸出格式。那么這個文件就是由 4 個 Section 組成。



假設格式的定義如下所示:

一般來說,對於 Header 或者 Trailer 中數據通常以描述性數據為主。比如像 Header 1 中的標題部分 - ADVENTUREWORKS EMPLOYEE INFORMATION 這些都是固定的長度。但同時也有動態變化的內容,比如 Header 1 中的文件輸出日期,以及 Header 2 中的員工數量。像 Trailer 部分,一般也是這種描述性的數據為主。
那么如何來輸出這種格式的數據,很顯然我們不能直接指定一個數據源表,然后將表中的內容給查詢出來然后控制好長度直接輸出,因為幾個不同 Section 部分的格式是不一樣的。我們的做法是,將 Header 1 和 Header 2 這樣占少量行的數據拼接成一個整體輸出;而對於像 Content 部分細節數據則使用數據庫表操作自然輸出。
同時,還要解決一個問題,如何讓這不同格式的 Section 能夠輸出到同一個文件?這些在下面的步驟中都將能看到。
下面這三部分的數據將為 OLE DB Source 提供數據查詢結果 -
對於 Header 1 和 Header 2 的處理
創建存儲過程,此存儲過程輸出一個兩行單列的表數據。
IF OBJECT_ID('T006_GET_EMPLOYEE_FILE_HEADERS') IS NOT NULL
DROP PROCEDURE T006_GET_EMPLOYEE_FILE_HEADERS GO
CREATE PROCEDURE T006_GET_EMPLOYEE_FILE_HEADERS AS
BEGIN
SET NOCOUNT ON
DECLARE @EMPLOYEE INT
SELECT @EMPLOYEE = COUNT(*) FROM T006_EMPLOYEE SELECT 'FILE CREATED DATE:' + -- Description
CONVERT(VARCHAR(12),GETDATE(),110) + -- File Created Date
SPACE(20) + -- 80 spaces
'**********' +
' ADVENTUREWORKS EMPLOYEE INFORMATION ' + -- Company Report Name
'**********' +
SPACE(95) AS HEADER UNION
SELECT 'TOTAL EMPLOYEES:' +
CONVERT(VARCHAR(10),@EMPLOYEE) +
SPACE(184-LEN(CONVERT(VARCHAR(10),@EMPLOYEE))) AS HEADER END
GO
對於 Trailer 的數據處理
創建存儲過程,輸出一個一行一列的表數據。
IF OBJECT_ID('T006_GET_EMPLOYEE_FILE_TRAILERS') IS NOT NULL
DROP PROCEDURE T006_GET_EMPLOYEE_FILE_TRAILERS GO
CREATE PROCEDURE T006_GET_EMPLOYEE_FILE_TRAILERS AS
BEGIN
SET NOCOUNT ON
SELECT REPLACE(SPACE(92),' ','*') +
' ADVENTUREWORKS ' +
REPLACE(SPACE(92),' ','*') AS TRAILER END
對於 Content 的數據處理
直接使用查詢即可,然后在輸出文件的時候確保各列輸出的格式與長度。
SELECT FirstName +' '+LastName AS CustomerName, Title, HireDate, BirthDate, EmailAddress, Phone, MaritalStatus FROM T006_EMPLOYEE
在控制流中,需要由3個數據流包構成,分別操作 Header 部分的輸出,Content 部分的輸出以及 Trailer 部分的輸出。

連接管理器 CM_FF_EMPLOYEE_HEADER 接受來自於 T006_GET_EMPLOYEE_FILE_HEADERS 存儲過程的輸出。

連接管理器 CM_FF_EMPLOYEE_CONTENT 接受 SELECT 查詢結果,同時要注意控制各列按指定寬度輸出。

連接管理器 CM_FF_EMPLOYEE_TRAILER 接受存儲過程 T006_GET_EMPLOYEE_FILE_TRAILERS 的輸出。

但是要注意,它們指向的都是同一個目標平面文件。當然,馬上就會有人問:那么不會出現后一個文件將前一個文件覆蓋掉的后果嗎?
這就是本案例處理的關鍵點所在 - 在 CM_FF_EMPLOYEE_HEADER 目標編輯器中選中 Overwrite data in the file - 覆蓋已存在的文件。

但是在后面兩個輸出中將不會選擇這個選項,那么后面兩個輸出 Content 和 Trailer 將會緊接着 Header 寫入到同一個文件中形成一個整體。

最終的輸出效果如下 -


處理平面文件還需要了解這幾篇文章
更多 BI 文章請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server) 如果覺得這篇文章看了對您有幫助,請幫助推薦,以方便他人在 BIWORK 博客推薦欄中快速看到這些文章。