微軟BI 之SSIS 系列 - 通過 OLE DB 連接訪問 Excel 2013 以及對不同 Sheet 頁的數據處理


文章更新歷史

2014年9月7日 - 加入了部分更新內容,在文章最后提到了關於不同 Office Excel 版本間的連接問題。

開篇介紹

這篇文章主要總結在 SSIS 中訪問和處理 Excel 數據的四個方面的主題內容 (都是處理以 .xlsx 結尾的 Excel 文件) -

  1. 如何在 SSIS 中集成對 Microsoft Excel 的訪問支持以及注意事項。
  2. 如何在 SSIS 中連接和訪問 Microsoft Excel 文件以及注意事項。
  3. 如何加載不同 Sheet 頁的數據到同一個表中。
  4. 如何加載不同 Sheet 頁的數據到不同的表中。

連接和處理 Microsoft Excel 文件的方式有很多種,包括使用 C#.NET 編程的形式加載處理數據,但本文只考慮在 SSIS 中如何加載 Microsoft Excel 文件中的數據。

在以前的 Microsoft Excel 文件版本中,使用 SQL Server 2008 R2 - BIDS 開發工具很容易處理,因為以前的版本是支持以 .xls 結尾的 Excel 文件,比如說 Excel 2003。Microsoft Office 版本升級之后,他們開始采用基於OpenXML的新的文件類型,也就是以 .xlsx 結尾的 Excel 文件類型。但是在 BIDS 中包括現在的 SQL Server 2012 - SSDT 版本的工具對以 .xlsx 結尾的 Excel 文件沒有直接驅動支持,因此需要人工的去配置一下。

在 SSIS 中集成對 Microsoft Excel 的訪問支持

在現有的 BIDS (SQL Server 2008 R2 及以前的版本中開發工具的簡稱) 和 SSDT (SQL Server 2012 的 BI 開發工具) 中打開一個 OLE DB Connection 看到的一些 OLE DB 的驅動。 為什么不去直接使用 Excel Source 組件? 因為 Excel Source 組件不支持以 .xlsx 后綴結尾的 Excel 連接和訪問,因此需要使用變通的 OLE DB 訪問方式。

需要去微軟官方網站下載並安裝驅動 - 點擊這里,可以選擇相應的語言,我選擇的是英文版本。

一定要注意的是,在這里只能選擇和安裝32位的安裝文件,因為我們的 BIDS 也好 SSDT 也好本身都是安裝的都是32位的環境,所以如果安裝了64位的驅動,BIDS 和 SSDT 也沒有辦法看到的。並且如果已經安裝了 64 位的 Microsoft Access Data Engine 2010,那么就需要先卸載下來,否則 32位版本的驅動是安裝不了的,低版本無法覆蓋高版本驅動。

安裝之后重啟 SSDT 就能看到這個驅動了,我們就可以使用這個驅動連接我們的 Excel 了。


在 SSIS 中連接和訪問 Microsoft Excel 文件

基於上面的驅動的安裝,我們可以做一個簡單的數據加載測試,同時里面也有一些注意事項是需要我們注意的。

測試目標表

USE BIWORK_SSIS
GO

IF OBJECT_ID('StaffExcel') IS NOT NULL
DROP TABLE StaffExcel 
GO

IF OBJECT_ID('DepartmentExcel') IS NOT NULL
DROP TABLE DepartmentExcel 
GO

CREATE TABLE DepartmentExcel
(
    ID INT,
    Department NVARCHAR(50),
    Manager NVARCHAR(50)
)

CREATE TABLE StaffExcel
(
    ID INT,
    FullName NVARCHAR(50),
    City NVARCHAR(50),
    Occupation NVARCHAR(50)
)

源數據是 Excel 的 Sheet 頁中的數據,並且我們的 Excel Sheet 頁上的數據格式應該是規范的,不規范的數據不適合通過這種方式處理。

我們要做的就是把 Sheet 頁是 Department 的數據從 Excel 導入到 DepartmentExcel 表中,新建一個 SSIS Package 並新建一個 OLE DB Connection。選擇 Microsoft Office 12.0 Access Database Engine OLE DB Provider,並指定 Excel 源文件的路徑和名稱。

注意在 All 頁面中,一直要指定 Excel 12.0 否則是無法連接到 Excel 數據源的。

拖放一個 Dataflow Task 並創建一個 OLE DB Source 並直接使用剛才創建好的 OLE DB Connection,指定 Sheet 名稱。

創建 OLE DB Connection 連接到目標表並創建 OLE DB Destination 連接到 OLE DB Source, 這時會出現警告。因為從 Excel 中加載的數據,默認格式都變成了 255 長度的NVARCHAR 數據類型,這樣會發生截斷並且目標表類型也不匹配。

警告內容:Validation warning. DST_Department: {35C08A6A-4279-4153-8D20-4ED829E68EF9}: Truncation may occur due to inserting data from data flow column "Department" with a length of 255 to database column "Department" with a length of 50. 

使用 Data Conversion 進行轉換, Output Alias 是從 Data Conversion 向下輸出的轉換后的別名。

轉換完了然后配置后面的 Data Mapping,注意 Data Mapping 中要 Map 的是 Output Alias 的那些 Column 而不是 Input Column 中的那些列。執行 SSIS Package 的時候出現錯誤,可以從下圖中看出來有以下兩種錯誤信息。

第一種說明是連接失敗,發生在 Task DST_Department 中:

[OLE_DB_SRC_Department [43]] Error: SSIS Error Code DTS_E_CANNOTACQUIRECONNECTIONFROMCONNECTIONMANAGER.  The AcquireConnection method call to the connection manager "E:\WorkSpace\Input\ExcelSource.xlsx" failed with error code 0xC0209303.  There may be error messages posted before this with more information on why the AcquireConnection method call failed.

第二種說明是數據源連接有問題,並且指明了錯誤的原因:因為我們的驅動是 32 位模式,但是這個包仍然是默認64位模式運行,因此在 64位運行環境下是無法檢測到這種只支持32位鏈接的驅動的。

[Connection manager "E:\WorkSpace\Input\ExcelSource.xlsx"] Error: The requested OLE DB provider Microsoft.ACE.OLEDB.12.0 is not registered. If the 64-bit driver is not installed, run the package in 32-bit mode. Error code: 0x00000000. An OLE DB record is available.  Source: "Microsoft OLE DB Service Components"  Hresult: 0x80040154  Description: "Class not registered".

在 SSIS 項目,右鍵屬性來修改包的運行模式改成 False 讓它在32位模式運行。

保存並運行成功,有4條數據順利加載到數據表中。

查詢 DepartmentExcel 表的結果。

如何加載不同 Sheet 頁的數據到同一個表中

這里不同的 Sheet 頁是指在同一個 Excel Source 文件中有很多的 Sheet 頁,但是這些 Sheet 頁中的數據格式都是一樣的,如下圖所示。

這個 Excel 文件有三個 Sheet 頁,一個是 Department,另外一個是我在博客園的ID - BIWORK,另外一個隨便就叫 Sheet1 了。 這種場景常常出現,比如有可能按天保存數據到同一個 Excel 文件中,每一個 Sheet 頁上的數據格式是一致的,那么就可以用我們這里的解決方案來完成了。

BIWORK 頁中的數據格式和 Department 中是一樣的,也就是 Column 列格式是一樣的, Sheet1 中也是如此。

這里可以考慮使用循環,思路是循環同一個 Excel 源上的 Sheet 頁,每次在循環中替換 Sheet 名就可以了。

新建一個字符串類型的變量,變量的名稱一定要是 Sheet Name,可以是任意中的一個比如 Department 或者 BIWORK,但一定要有,后綴加上 $。

新建一個 OLE DB Connection,操作方法和上面的示例一樣的,別忘記修改 All 里面的 Excel 12.0。但是一定要注意,在我們這個例子有,是有兩個連接到同一個 Excel 源的 OLE DB 連接的。一個是下面用來循環遍歷 Sheet Name 的,一個是用來加載數據的。

添加一個 Foreach Loop 容器,按照下圖顯示操作和配置,這里的循環作用是循環指定 EXCEL 源上的 Table Name 即 Sheet Name。這個鏈接源就是上面一個步驟創建好的,再次強調 - 它只用來遍歷 Excel Sheet Name,不會和數據有任何交互!

在變量配置中,指定 SheetName 變量來接受從 Excel 源中遍歷來的 SheetName。特別要說明的是,這里的 Index 只能寫成 2,表示 SheetName。它並不表示 Sheet 頁的數量,這一點一定要注意,嘗試使用 Index 0 或者 1 是取不到任何有意義的東西的。

把示例一種的 Data Flow Task 直接拷貝放入這個容器中,然后修改一下 OLE DB Source ,因為此時的表名是由變量來決定的了。

保存並運行,要注意到 ExcelSourceSame 和 中間的那個連接管理器其實都是連接到同一個數據源,但是它們的作用不同。

剛才新添加了幾個 Sheet,名字隨便取。

執行完上面的包之后查詢一下數據庫,不同 Sheet 頁中的數據全部加載到同一張表了。

如何加載不同 Sheet 頁的數據到不同的表中

所謂不同的 Sheet 頁是指在同一個 Excel 數據源中的 Sheet 頁中的數據格式不同,要把它們分別加載到不同的表中。

基於上面的示例 Excel,我加上一個 Staff 頁數據,這個頁的數據目標表是文章開頭處創建的 StaffExcel 表。

所以在這個示例中,Excel 源中的 Sheet 頁與數據庫中的表就要求分開被處理了。

Sheet - Department, BIWORK, Sheet1, Sheet2, Sheet3 都應該指向 DepartmentExcel。

Sheet - Staff 指向 StaffExcel。

要解決這個問題首先要明白在建立從數據源到目的表的過程中,需要提前建立好數據源列到目標表列的 Column Mapping。因此不能使用 Department Sheet 到 DepartmentExcel 表的 Data Mapping 來代替 Staff Sheet 到 StaffExcel 表的 Data Mapping,所以動態根據表名直接連接到目的表而想繞過 Data Mapping 這一點很難做到。

那這里有一種變通的方法,就是先為源與目標表建立好 Data Mapping,然后根據對 Sheet Name 的判斷自動將 Sheet 分發到不同的 Data Mapping。

對上面的例子做出如下修改:

1. 添加一個 Mapping 表,用來將 Sheet Name 進行歸類,這種是需要提前收集的。

在一個自動化的操作流程中,這個數據是可以實現自動添加的。因為首先 Excel 源本身也有可能就是通過程序輸出的,因此在輸出的過程中是可以對 SheetName 的命名做出要求的,包括一個 Excel 上的 Sheet 表 Mapping 分類。那么這種相對規范的 Excel 表是完全有可能適用於我們這個示例提出的解決方案的,除此之外一切不規范的 Excel 建議通過 C# Script 解決。

IF OBJECT_ID('TableSheetMapping') IS NOT NULL
DROP TABLE TableSheetMapping 
GO

CREATE TABLE TableSheetMapping
(
    ID INT IDENTITY(1,1) PRIMARY KEY,
    DestTableName NVARCHAR(50),
    SheetName NVARCHAR(50)
)

INSERT INTO TableSheetMapping VALUES
('DepartmentExcel','Department'),
('DepartmentExcel','BIWORK'),
('DepartmentExcel','Sheet1'),
('DepartmentExcel','Sheet2'),
('DepartmentExcel','Sheet3'), 
('StaffExcel','Department')

2. 添加變量。

  1. SheetName - 是從 Foreach Loop 循環得到的 Sheet 頁的名稱,但是只僅僅通過 N 多的 Sheet 頁是不知道要匹配對應的表的。
  2. TableName - 因此這里的 TableName 就是根據 Sheet 頁的名稱到數據庫中去查來的一個結果。
  3. DepartmentSheet - 用來指定與 DepartmentExcel 表建立連接的 SheetName。
  4. StaffSheet - 同 DepartmentSheet。

因此只有添加了 Department$ 和 Staff$,OLE DB Source 才知道數據源是什么樣的數據結構,該如何與下游目標表與匹配。

3. 添加一個 Staff Data Flow Task,並且修改相應的配置。

注意 Department Data Flow Task 也需要修改要將 SheetName 換成 DepartmentSheet。

下面是新添加的 DST_Staff_Sheet 控制流,命名應該叫 DFT,是我寫錯了就不改了。

DST_Staff_Sheet 中三個組件都需要修改。

OLE_DB_SRC_Staff 指定表名變量,其它配置省略。

那么現在要做的事情就是分發 Sheet 了,在 Foreach Loop 中添加一個 Execute SQL Task 用來根據 SheetName 獲取 Table 名稱。

參數變量 SheetName 是從 Loop 循環得到的。

查詢的結果保存在 TableName 變量中。

新添加一個 Script Task 並且傳入變量。

Script Task 中的邏輯主要是用來接收 TableName,根據TableName判斷來到底是 DepartmentSheet 變量還是 StaffSheet 變量來接受循環中產出的 Sheet 名稱,這樣下游的 Data Flow Task 就可以根據 SheetName$ 來獲取數據源的數據通道了。

public void Main()
        {
            // TODO: Add your code here
            string tableName = Dts.Variables["User::TableName"].Value.ToString();
            string sheetName = Dts.Variables["User::SheetName"].Value.ToString();

            if (tableName == "DepartmentExcel")
                Dts.Variables["User::DepartmentSheet"].Value = sheetName;
            
            if (tableName == "StaffExcel")
                Dts.Variables["User::StaffSheet"].Value = sheetName;

            Dts.TaskResult = (int)ScriptResults.Success;
        }

最后要做的一件事情就是使用條件表達式決定流程的走向,根據表名來判斷 Script Task 的輸出。

 

保存並執行 SSIS Package,所有的數據都分散到不同的執行流程中了。

查詢一下數據庫,數據全部進去了並且 Department 和 Staff 是從不同的 Sheet 頁上取到的數據。

問題

為什么看到 TableName 在邏輯判斷的過程中出現了好幾次?

我們假設是一個 Excel 文件中 Sheet 頁比較多的情況,因此使用了一個數據庫 Mapping 表,根據 Mapping 表來看一次循環中當前的 Sheet 頁是屬於哪一個目標表的數據源。

根據 SheetName 取到 Table Name 之后,需要在 C# Script 中使用 Table Name 來決定在下面的兩個 Data Flow Task 中表變量 DepartmentSheet 和 StaffSheet 到底是哪一個源 Sheet。

確定好了源 Sheet 后,再通過 Table Name 作為條件約束控制流程的分支走向,這樣就避免了多次循環同一 Sheet 的問題。

總結

其實在 SSIS 中處理 Excel 包括連我自己都不太喜歡這種方式,因為經常會碰到連接的問題,字符串格式轉換的問題,不規范的數據格式問題等等。越是不規范的 Excel 文件越是應該在源頭控制它,比如在 Excel 文件輸出的時候通常會告訴上游產出者我們對 Excel 文件格式的要求。不要求做到絕對規范,但是最起碼會做到一個 Excel 文件只保留一個 Sheet,一個 Excel 只表示一個數據源。這樣在下游 SSIS 處理中就會減少很多的工作量,因此在數據整理收集階段,溝通的工作是非常非常重要的,上游數據源一個很小的改動或者格式調整對下游數據處理就會產生極大的或好或壞的影響。

2014年9月7日 后記更新

非常奇怪的一件事情發生了!在我今天的一次測試中,我發現只要安裝完了 AccessDatabaseEngine 之后,我完全就可以直接使用 Excel Source 和 Excel Destination 就可以操作 Excel 數據的抽取和導出了!並且在 64 位下也是可以正常運行的,不需要調整成 32 位的模式。

直接使用 Microsoft Excel 2007 也是可以的。

在檢查它的 Provider 時,發現它本質上仍然是 -

Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\BIWORKSPACE_FILE\TS_BIWORK_SSIS\INPUT_DIRECTORY\012\Hotel.xlsx;Extended Properties="EXCEL 12.0 XML;HDR=YES";

我能想起來的區別就是:

本文中測試的環境安裝的 Office 版本是 Office 365,而現在新的測試環境 2014年9月7日 - 是 Microsoft Office Professional Plus 2013。

現在唯一的問題是,由於我在寫這篇文章時的測試環境已經丟失,我現在安裝的都是 Professional Plus 2013,所以無法還原在寫這篇文章時的配置,包括報錯信息的出現以及問題解決前后的失敗與成功對比。所以希望大家在看到這篇文章的時候,如果有可能也幫助驗證一下在自己當前版本下關於這個 Excel 的連接情況。

但無論如何,多了一種選擇,多了一種解決方案,希望能夠幫助到大家。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM