🧾【完整教學】SQL Server 大量匯入常見錯誤 4866/7301 一次搞懂與修好(含範例與檢查清單)

 

摘要(給新手也看得懂)

把大量資料(CSV/TSV)匯進 SQL Server 時,最常遇到兩個錯誤:

  • 訊息 4866:系統說「某一列的欄位太長」。多半不是資料真的太長,而是分隔符或換行符設定錯,導致整行被塞進第一欄;或是編碼/BOM讓第一個欄位吃到怪字元。

  • 訊息 7301:系統說找不到 IID_IColumnsInfo。意思是你用 OPENROWSET(BULK …) 時,引擎拿不到欄位結構;改用 BULK INSERT 或提供解析設定就會好很多。

本篇用工程師的方式,教你從檔案偵錯 → 正確匯入 → 問題復原的完整流程。所有範例皆為示意,與真實專案無關


    錯誤 4866 與 7301 到底在說什麼?

    • 4866:資料行太長
      直覺以為是欄位長度不夠,但 7 成以上其實是分隔字元/換行符/編碼設定錯,導致整列被塞進第一欄。

    • 7301:IID_IColumnsInfo
      代表 OPENROWSET(BULK…) 想拿欄位結構卻拿不到。解法是改用 BULK INSERT、或在 OPENROWSET 明確指定格式(例如 FORMAT='CSV')、或使用格式檔


    5 分鐘快速檢查清單(先做這個!)

    1. 檔案是 CSV 還是 TSV?分隔字元是逗號還是 Tab?

    2. 換行是 LF (0x0A) 還是 CRLF (0x0D0A)

    3. 編碼是 UTF-8(有/無 BOM) 還是 UTF-16 LE

    4. 第一列是否標題?要不要 FIRSTROW = 2

    5. 目標表欄位長度夠嗎?必要時先匯 staging 表 再轉型。

    6. 檔案路徑對 SQL 服務帳號是否可讀?(不是你的 Windows 帳號)


    先「看」檔案:用 SQL 預覽前幾行(判斷分隔/換行/編碼)

    這段只把整個檔案當成文字看,不牽涉欄位結構,所以不會踩 7301


    -- 範例檔:C:\Import\data_utf8.tsv(示意)
    SELECT TOP (3) CAST(BulkColumn AS nvarchar(max)) AS line_preview FROM OPENROWSET( BULK 'C:\Import\data_utf8.tsv', SINGLE_CLOB, CODEPAGE = '65001' -- UTF-8 ) AS src;

    你會得到什麼?

    • 看到實際分隔(,\t

    • 看到換行(行尾是否多個空白或奇怪符號)

    • 看第一個字是否出現怪字元(可能是 BOM)


    最穩的匯入法:BULK INSERT(CSV/TSV)

    SQL Server 2017+ 支援 FORMAT='CSV'。TSV 也可搭配 FIELDTERMINATOR 指定 Tab。

    -- 將資料匯入到正式表 dbo.ImportCustomers(示意用) BULK INSERT dbo.ImportCustomers FROM 'C:\Import\data_utf8.tsv' WITH ( FORMAT = 'CSV', FIELDTERMINATOR = '\t', -- TSV 用 Tab ROWTERMINATOR = '0x0a', -- Linux LF;若是 Windows 檔案改 '0x0d0a' FIELDQUOTE = '"', CODEPAGE = '65001', -- UTF-8 FIRSTROW = 2, -- 跳過標題列(若沒有標題就移除) KEEPNULLS, ERRORFILE = 'C:\Import\bulk_errors.err', MAXERRORS = 1000, TABLOCK );

    舊版 SQL Server 不支援 FORMAT='CSV'?改用傳統語法:


    BULK INSERT dbo.ImportCustomers FROM 'C:\Import\data_utf8.tsv' WITH ( DATAFILETYPE = 'char', FIELDTERMINATOR = '0x09', -- Tab ROWTERMINATOR = '0x0a', -- 或 '0x0d0a' CODEPAGE = '65001', FIRSTROW = 2, ERRORFILE = 'C:\Import\bulk_errors.err', MAXERRORS = 1000 );
    你的環境不吃 UTF-8?

    把檔案另存 UTF-16 LE(含 BOM),並使用:


    BULK INSERT dbo.ImportCustomers FROM 'C:\Import\data_utf16.tsv' WITH ( DATAFILETYPE = 'widechar', -- UTF-16 LE FIELDTERMINATOR = '0x09', ROWTERMINATOR = '0x0d0a', -- 若檔案是 CRLF FIRSTROW = 2 );

    用 staging 表避開欄位長度問題(4866 終結者)

    先匯到寬欄位的暫存表,再清理轉進正式表,就不怕 4866 連環爆。

    -- 1) 建 staging 表(欄位名稱與數量依你的檔案為準;以下示意 6 欄) CREATE TABLE dbo.ImportCustomers_stg ( c1 nvarchar(4000), c2 nvarchar(4000), c3 nvarchar(4000), c4 nvarchar(4000), c5 nvarchar(4000), c6 nvarchar(4000) ); -- 2) 匯入 staging(沿用你確認過的分隔/換行/編碼) BULK INSERT dbo.ImportCustomers_stg FROM 'C:\Import\data_utf8.tsv' WITH ( FORMAT = 'CSV', FIELDTERMINATOR = '\t', ROWTERMINATOR = '0x0a', CODEPAGE = '65001', FIRSTROW = 2, ERRORFILE = 'C:\Import\bulk_errors.err' ); -- 3) 清理/轉型 → 正式表(長度截斷、日期/數字安全轉換) INSERT dbo.ImportCustomers (CustomerId, FullName, Email, Age, CreatedOn, IsActive) SELECT TRY_CONVERT(int, c1) AS CustomerId, LEFT(c2, 120) AS FullName, LEFT(c3, 200) AS Email, TRY_CONVERT(int, c4) AS Age, TRY_CONVERT(datetime2(0), c5) AS CreatedOn, CASE WHEN c6 IN (N'1', N'true', N'Y') THEN 1 ELSE 0 END AS IsActive FROM dbo.ImportCustomers_stg;

    一定要 OPENROWSET?三種安全用法

    1. 只讀整檔(不需欄位介面):不會踩 7301

    SELECT TOP (1) CAST(BulkColumn AS nvarchar(max)) AS sample FROM OPENROWSET(BULK 'C:\Import\data_utf8.csv', SINGLE_CLOB, CODEPAGE = '65001') AS s;
    1. CSV 解析器:提供欄位抽象給引擎

    SELECT TOP (10) * FROM OPENROWSET( BULK 'C:\Import\data_utf8.csv', FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '0x0a', CODEPAGE = 65001 ) AS r;
    1. 格式檔(Format File):把每欄型別/長度寫清楚(XML 或非 XML 格式皆可)。

    適合複雜檔案或要長期維護的匯入流程。



    編碼與換行:UTF-8、UTF-16、LF/CRLF 一次分清

    • UTF-8CODEPAGE='65001'。若是舊版 SQL 或工具不穩,改用 UTF-16 LE + DATAFILETYPE='widechar'

    • LF (0x0A):Linux/Mac 常見。

    • CRLF (0x0D0A):Windows 常見。

    • BOM:UTF-8 with BOM 會讓第一欄多出不可見的 BOM,導致 4866。可在匯入時用對的 CODEPAGE,或先把檔案另存「UTF-8(無 BOM)」。


    路徑與權限:為什麼明明我看得到檔案,SQL 卻讀不到?

    • SQL Server 讀檔是用服務帳號的權限(例如 NT SERVICE\MSSQLSERVER 或你自訂的帳號),不是你登入 SSMS 的個人帳號。

    • 建議把匯入檔放在 SQL Server 主機本機磁碟(如 C:\Import),並授予該服務帳號「讀取」權限。

    • 若用 網路分享路徑(UNC),服務帳號若不是網域帳號,通常會讀不到。


    命令列備案:用 bcp 快速驗證設定

    想用最少參數就測分隔/換行/編碼是否正確,bcp 很好用:

    bcp dbo.ImportCustomers in "C:\Import\data_utf8.tsv" -S . -T -c -t \t -r \n -C 65001 -e "C:\Import\bcp_error.log"
    • -t \t 指定 Tab 分隔

    • -r \n-r \r\n 指定換行

    • -C 65001 指定 UTF-8


    常見地雷與對策總表

    症狀 可能原因 對策
    4866:第一欄超長 分隔/換行錯、UTF-8 BOM 確認 FIELDTERMINATORROWTERMINATORCODEPAGE;或先匯 staging
    7301:IID_IColumnsInfo OPENROWSET 沒欄位結構 BULK INSERT、或 FORMAT='CSV'、或格式檔、或 SINGLE_CLOB
    匯入亂碼 編碼不符 UTF-8:CODEPAGE=65001;UTF-16:DATAFILETYPE='widechar'
    某些列失敗 特殊字元、引號、逗號 FIELDQUOTE='"';清洗資料或改用 staging
    我看得到檔案但 SQL 看不到 服務帳號沒權限 把檔案放本機並授權 SQL 服務帳號讀取
    標題被當資料 忘記跳過第一列 FIRSTROW = 2

    FAQ

    Q1:我的檔案是 CSV 不是 TSV,要改什麼?
    A:把 FIELDTERMINATOR='\t' 改成 ',',其他設定相同。

    Q2:到底用 BULK INSERT 還是 OPENROWSET?
    A:匯入正式表推薦 BULK INSERT(穩、直覺);預覽與快速分析OPENROWSET(SINGLE_CLOB) 最不會踩雷。

    Q3:為什麼有時候 UTF-8 還是會出事?
    A:BOM、舊版 SQL、或來源工具導出的奇怪控制字元都可能影響。遇到就把檔案另存 UTF-16 LE + widechar,或先清洗字元。

    Q4:欄位偶爾會超長,怎麼辦?
    A:先到 staging(nvarchar(4000)),再用 LEFT()/TRY_CONVERT() 轉型到正式表。


    結語

    匯入錯誤 4866 與 7301 看似可怕,但其實是格式偵測解析方式沒對齊。只要按本文流程:先預覽 → 定義分隔/換行/編碼 → 優先用 BULK INSERT → 必要時 staging → 權限路徑對齊,大多數問題都能在 15 分鐘內搞定。

    留言

    這個網誌中的熱門文章

    🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

    🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)

    🛠【ASP.NET Core + Oracle】解決 ORA-00904 "FALSE": 無效的 ID 錯誤與資料欄位動態插入顯示問題