前置環境設定
- 啟用 Ad Hoc Distributed Queries:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'Ad Hoc Distributed Queries', 1; RECONFIGURE;
- 安裝 ACE 驅動 (64 位元 SQL Server 必裝 64 位元驅動):
- 設定提供者屬性 (SSMS 物件總管 → Server Objects → Linked Servers → Providers → Microsoft.ACE.OLEDB.12.0 → 勾選 Dynamic Parameters、Allow Inprocess、DisallowAdhocAccess=0)
方案一:OPENROWSET 單次查詢 (適合臨時分析)
讀取 Excel (.xlsx/.xls)
-- .xlsx (Excel 12.0 Xml)
SELECT * FROM OPENROWSET(
'Microsoft.ACE.OLEDB.12.0',
'Data Source=D:\Data\Sales.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"',
'SELECT * FROM [Sheet1$]');
-- .xls (Excel 8.0)
SELECT * FROM OPENROWSET(
'Microsoft.ACE.OLEDB.12.0',
'Data Source=D:\Data\Sales.xls;Extended Properties="Excel 8.0;HDR=YES;IMEX=1"',
'SELECT * FROM [Sheet1$]');
-- 指定範圍
SELECT * FROM OPENROWSET(
'Microsoft.ACE.OLEDB.12.0',
'Data Source=D:\Data\Sales.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"',
'SELECT * FROM [Sheet1$A1:D100]');
讀取 CSV/TXT 文字檔
-- 使用 Jet/ACE Text 驅動
SELECT * FROM OPENROWSET(
'Microsoft.ACE.OLEDB.12.0',
'Text;HDR=YES;FORMAT=CSVDelimited;Database=D:\Data\',
'SELECT * FROM [Sales.csv]');
-- 或指定分隔符 (Tab 等)
SELECT * FROM OPENROWSET(
'Microsoft.ACE.OLEDB.12.0',
'Text;HDR=YES;FORMAT=TabDelimited;Database=D:\Data\',
'SELECT * FROM [Sales.txt]');
方案二:OPENDATASOURCE 臨時連接 (語法較簡潔)
SELECT * INTO #TempSales
FROM OPENDATASOURCE(
'Microsoft.ACE.OLEDB.12.0',
'Data Source=D:\Data\Sales.xlsx;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"'
)...[Sheet1$];
方案三:建立連結伺服器 (適合重複存取)
EXEC sp_addlinkedserver
@server = 'LINK_EXCEL_SALES',
@srvproduct = '',
@provider = 'Microsoft.ACE.OLEDB.12.0',
@datasrc = 'D:\Data\Sales.xlsx',
@provstr = 'Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1"';
-- 查詢
SELECT * FROM LINK_EXCEL_SALES...[Sheet1$];
-- 移除
EXEC sp_dropserver 'LINK_EXCEL_SALES', 'droplogins';
方案四:外部表 - PolyBase (SQL Server 2019+ 推薦,效能最佳)
建立外部資料源
CREATE EXTERNAL DATA SOURCE ExcelSales
WITH (
LOCATION = 'D:\Data\',
PUSH_DOWN = OFF
);
建立外部檔案格式
CREATE EXTERNAL FILE FORMAT CSVFormat
WITH (
FORMAT_TYPE = DELIMITEDTEXT,
FORMAT_OPTIONS (
FIELD_TERMINATOR = ',',
STRING_DELIMITER = '"',
FIRST_ROW = 2, -- 跳過標題
USE_TYPE_DEFAULT = TRUE
)
);
建立外部表
CREATE EXTERNAL TABLE dbo.Sales_External (
OrderID INT,
CustomerName NVARCHAR(100),
Amount DECIMAL(18,2),
OrderDate DATE
)
WITH (
DATA_SOURCE = ExcelSales,
FILE_FORMAT = CSVFormat,
LOCATION = 'Sales.csv',
REJECT_TYPE = VALUE,
REJECT_VALUE = 100
);
查詢外部表 (支援 predicate pushdown、統計資訊)
SELECT CustomerName, SUM(Amount) AS TotalAmount
FROM dbo.Sales_External
WHERE OrderDate >= '2024-01-01'
GROUP BY CustomerName
ORDER BY TotalAmount DESC;
Extended Properties 關鍵參數詳解
| 參數 | 說明 | 建議值 |
| HDR | 第一列為標題 | YES (預設) / NO |
| IMEX | 混合型別導入模式 | 1 = 文字模式 (避免數值/文字混雜導致 NULL),最重要參數 |
| FORMAT | 文字檔格式 | CSVDelimited, TabDelimited, Delimited(自定義) |
| MAXSCANROWS | 型別推斷掃描行數 | 0 = 全掃描 (預設 8),大檔案建議 0 |
| CharacterSet | 編碼 | 65001 (UTF-8), 936 (GBK), 1252 (ANSI) |
Schema.ini 精確控制欄位型別 (文字檔專用)
在文字檔同目錄建立 Schema.ini:
[Sales.csv]
ColNameHeader=True
Format=CSVDelimited
CharacterSet=65001
Col1=OrderID Long
Col2=CustomerName Text Width 100
Col3=Amount Currency
Col4=OrderDate DateTime
驅動將依此定義強制轉換,解決型別推斷錯誤。
常見錯誤排查
| 錯誤 | 原因 | 解決 |
| 找不到提供者 | ACE 驅動未安裝或位元數不符 | 安裝 64 位元 ACE,/passive 參數 |
| 無法啟動 OLE DB 提供者 | Ad Hoc 未啟用 / DisallowAdhocAccess=1 / 權限不足 | 啟用選項、註冊表設 0、服務帳戶授權資料夾讀取 |
| 數值欄位讀成 NULL | 混合型別 (前幾行純數字,後面含文字) | IMEX=1、Schema.ini、或先轉 CSV 全文加引號 |
| 中文亂碼 | 編碼不匹配 | CharacterSet=65001 (UTF-8) 或 936 (GBK),確認檔案實際編碼 |
| 工作表名稱錯誤 | 中文名需方括號+$,隱藏表雙$ | [工作表$], [Sheet1$], [Sheet1$$] |
效能與最佳實踐比較
| 方案 | 效能 | 功能豐富度 | 維護性 | 推薦場景 |
| OPENROWSET/OPENDATASOURCE | 中 (單執行緒、無統計資訊) | 基本查詢 | 臨時、腳本內嵌 | 一次性分析、DBA 臨時查詢 |
| 連結伺服器 | 中 | 可建立視圖/預存程序封裝 | 固定檔案、重複存取 | 部門級固定報表來源 |
| PolyBase 外部表 | 高 (平行、Predicate Pushdown、統計資訊) | 完整關聯運算、可建立索引視圖 | 元資料管理、權限控制 | 企業級資料湖、生產 ETL 來源 |
| SSIS / Azure Data Factory | 最高 (平行、緩衝、錯誤流、增量) | 完整 ETL 能力 | 專案部署、版控、監控 | 核心資料倉庫載入管線 |
自動化匯入範本:參數化預存程序
CREATE PROCEDURE usp_ImportFile
@FilePath NVARCHAR(500),
@FileType NVARCHAR(20) = 'EXCEL', -- EXCEL, CSV, TXT
@SheetName NVARCHAR(100) = 'Sheet1',
@TargetTable NVARCHAR(200),
@TruncateTarget BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Provider NVARCHAR(100) = 'Microsoft.ACE.OLEDB.12.0';
DECLARE @ExtProps NVARCHAR(500);
DECLARE @SQL NVARCHAR(MAX);
IF @FileType = 'EXCEL' BEGIN
IF RIGHT(@FilePath, 5) = '.xlsx'
SET @ExtProps = 'Excel 12.0 Xml;HDR=YES;IMEX=1';
ELSE
SET @ExtProps = 'Excel 8.0;HDR=YES;IMEX=1';
END ELSE IF @FileType = 'CSV' BEGIN
SET @ExtProps = 'Text;HDR=YES;FORMAT=CSVDelimited;IMEX=1;Database=' + LEFT(@FilePath, LEN(@FilePath) - CHARINDEX('\', REVERSE(@FilePath)));
END
IF @TruncateTarget = 1
SET @SQL = 'TRUNCATE TABLE ' + QUOTENAME(@TargetTable) + '; ';
SET @SQL += 'INSERT INTO ' + QUOTENAME(@TargetTable) + ' SELECT * FROM OPENROWSET('''
+ @Provider + ''', ''' + @ExtProps + ''', ''SELECT * FROM [' + @SheetName + '$]'')';
EXEC sp_executesql @SQL;
END;