SQL Server BULK INSERT无法加载最后一行数据求助
我之前碰到过一模一样的情况——CSV最后一行没带你指定的行终止符0x0a,导致BULK INSERT直接跳过这一行,还连个报错都没有,确实挺坑的。既然你没法修改源CSV文件,给你几个可行的解决方案:
方案1:用XML格式化文件(最推荐,性能最优)
BULK INSERT对格式化文件的行终止规则支持更灵活,能兼容最后一行没有行终止符的情况。你可以创建一个XML格式的格式化文件,明确定义每个字段的分隔符和行终止符:
假设你的目标表有3个字段(可根据实际字段数调整),创建一个名为C:\MyFormatFile.xml的文件,内容如下:
<?xml version="1.0"?> <BCPFORMAT xmlns="http://schemas.microsoft.com/sqlserver/2004/bulkload/format" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <RECORD> <FIELD ID="1" xsi:type="CharTerm" TERMINATOR="~%" MAX_LENGTH="255"/> <!-- 第一个字段的分隔符 --> <FIELD ID="2" xsi:type="CharTerm" TERMINATOR="~%" MAX_LENGTH="255"/> <!-- 第二个字段的分隔符 --> <FIELD ID="3" xsi:type="CharTerm" TERMINATOR="0x0a" MAX_LENGTH="255"/> <!-- 最后一个字段用行终止符 --> </RECORD> <ROW> <COLUMN SOURCE="1" NAME="你的字段1名" xsi:type="SQLVARCHAR"/> <COLUMN SOURCE="2" NAME="你的字段2名" xsi:type="SQLVARCHAR"/> <COLUMN SOURCE="3" NAME="你的字段3名" xsi:type="SQLVARCHAR"/> </ROW> </BCPFORMAT>
然后修改你的BULK INSERT语句,加上FORMATFILE参数,去掉原来的FIELDTERMINATOR和ROWTERMINATOR(因为已在格式化文件中定义):
BULK INSERT 你的目标表名 FROM 'C:\你的源CSV文件路径.csv' WITH ( MAXERRORS = 1000000, CODEPAGE = 1251, ERRORFILE = 'C:\MyFile_BadData.log', FORMATFILE = 'C:\MyFormatFile.xml' );
这个方法会让BULK INSERT正确识别最后一行,即使它没有行终止符,因为格式化文件会告诉它读取到文件末尾就结束当前行。
方案2:用OPENROWSET批量导入后拆分字段
如果不想创建额外的格式化文件,你可以先用OPENROWSET把整个CSV的内容按行读入临时表,再手动拆分字段到目标表。这种方法完全绕过了BULK INSERT对行终止符的严格校验:
-- 创建临时表存储原始行数据 CREATE TABLE #TempRawLines (RawContent VARCHAR(MAX)); -- 把CSV所有内容(包括最后一行)导入临时表 INSERT INTO #TempRawLines SELECT BulkColumn FROM OPENROWSET(BULK 'C:\你的源CSV文件路径.csv', SINGLE_CLOB) AS ImportData; -- 拆分每行的字段到目标表(这里假设你有3个字段,按需调整) INSERT INTO 你的目标表名(字段1, 字段2, 字段3) SELECT -- 提取第一个字段 LEFT(RawContent, CHARINDEX('~%', RawContent) - 1) AS 字段1, -- 提取第二个字段 SUBSTRING( RawContent, CHARINDEX('~%', RawContent) + 2, CHARINDEX('~%', RawContent, CHARINDEX('~%', RawContent) + 2) - CHARINDEX('~%', RawContent) - 2 ) AS 字段2, -- 提取第三个字段(最后一个字段,直接取分隔符之后的内容) RIGHT(RawContent, LEN(RawContent) - CHARINDEX('~%', RawContent, CHARINDEX('~%', RawContent) + 2) - 1) AS 字段3 FROM #TempRawLines; -- 清理临时表 DROP TABLE #TempRawLines;
如果你的字段数量较多,或者字段内容可能包含特殊字符,建议写一个自定义的字符串拆分函数来替代上面的硬编码拆分逻辑,这样更灵活。
方案3:调整ROWTERMINATOR参数(快速尝试)
你可以试试把ROWTERMINATOR = '0x0a'改成ROWTERMINATOR = '\n',有时候BULK INSERT对十六进制和字符形式的行终止符处理逻辑略有差异,这个小改动可能就解决问题了。修改后的语句:
BULK INSERT 你的目标表名 FROM 'C:\你的源CSV文件路径.csv' WITH ( MAXERRORS = 1000000, CODEPAGE = 1251, FIELDTERMINATOR = '~%', ROWTERMINATOR = '\n', ERRORFILE = 'C:\MyFile_BadData.log' );
不过这个方法的稳定性不如前两个,毕竟不同系统的换行符(LF/CRLF)可能有差异,但值得快速试一下。
内容的提问来源于stack exchange,提问作者Karthick88it

