我正在尝试构建一个将 Apple EPF 提要数据导入 SQL Server 2012 实例的应用程序。数据以平面文件的形式提供,这些文件使用 CHAR(1) 字段分隔符和 CHAR(2) 加上换行符(char (10)) 作为行分隔符。因此,简化的 3 字段行如下所示:
field1value[char(1)]field2value[char(1)]field3value[char(2)][linefeed]
field1value[char(1)]field2value[char(1)]field3value[char(2)][linefeed]
这些文件以 UTF-8 编码并包含许多不同的语言,重要的是要保留所有数据,因此不能选择向下编码为 ASCII。但是,SQL Server 不支持 UTF-8 的 BULK INSERT。
我将其编写为 C# 命令行应用程序,并且我正在使用以下方法预处理每个文件,该方法去除文件末尾的注释行和最后的换行符,并将文件从 UTF-8转为 UTF-16:
public void PrepareAppleEPFFile(string dataFilePath, string cleanedFilePath)
{
if (File.Exists(cleanedFilePath))
{ return; } // Skip processing if file already exists
using (StreamReader reader = new StreamReader(dataFilePath, Encoding.UTF8))
{
using (StreamWriter writer = new StreamWriter(cleanedFilePath, false, Encoding.Unicode))
{
string line;
bool firstLine = true;
while (!reader.EndOfStream)
{
line = reader.ReadLine();
if (line.Length > 0 && line.Substring(0, 1) != "#") // Skip empty and commented lines
{
// Done this way to avoid adding a trailing newline, which breaks BULK INSERT
if (!firstLine)
{
writer.Write("\n");
}
writer.Write(line);
firstLine = false;
}
}
}
}
}
准备好文件后,我使用以下 BULK INSERT 语句:
BULK INSERT dbo.Application
FROM 'C:\iTunes\data\application.cleaned'
WITH (
TABLOCK,
DATAFILETYPE = 'widechar',
ERRORFILE = 'C:\iTunes\Logs\application.log',
FORMATFILE = 'C:\iTunes\FormatDefinitions\Application.xml'
)
它使用以下 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="\x01\x00"/>
<FIELD ID="2" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="3" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="4" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="5" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="6" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="7" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="8" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="9" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="10" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="11" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="12" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="13" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="14" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="15" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="16" xsi:type="CharTerm" TERMINATOR="\x01\x00"/>
<FIELD ID="17" xsi:type="CharTerm" TERMINATOR="\x02"/>
</RECORD>
<ROW>
<COLUMN SOURCE="2" NAME="application_id" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="3" NAME="title" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="4" NAME="recommended_age" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="5" NAME="artist_name" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="6" NAME="seller_name" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="7" NAME="company_url" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="8" NAME="support_url" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="9" NAME="view_url" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="10" NAME="artwork_url_large" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="11" NAME="artwork_url_small" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="12" NAME="itunes_release_date" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="13" NAME="copyright" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="14" NAME="description" xsi:type="SQLNTEXT"/>
<COLUMN SOURCE="15" NAME="version" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="16" NAME="itunes_version" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="17" NAME="download_size" xsi:type="SQLNVARCHAR"/>
<COLUMN SOURCE="1" NAME="export_date" xsi:type="SQLNVARCHAR"/>
</ROW>
</BCPFORMAT>
上面的方案似乎几乎可以工作,但是插入到表中的数据(使用所有 NVARCHAR/NTEXT 列)在每个字符后面都返回了 CHAR(0)。因此,如果列中的值应该是“Bob”,我实际得到的是“B[char(0)]o[char(0)]b[char(0)]”,其中 [char(0)] course 实际上显示为非打印字符显示的标准空方格。
以下查询进一步说明了该问题:
SELECT TOP 1
DATALENGTH(title) AS DATALENGTH_of_title,
DATALENGTH(cast(title as varchar(1000))) AS DATALENGTH_of_VARCHAR_CAST_Title
FROM dbo.Application
对于具有 7 个字符标题的行,上述查询为 DATALENGTH_of_title 返回值 28,为 DATALENGTH_of_VARCHAR_CAST_Title 返回值 14。这些值是应有的两倍。BULK INSERT 似乎试图重新编码已经 unicode 的数据,从而导致双编码数据,其中每个字符后跟 3 个 CHAR(0)s 而不是一个。
另一个不太严重的问题是,以 UTF-16 格式写入数据文件会导致将 BOM 添加到文件的开头,并且该 BOM 序列最终会作为第一行中第一个字段值的一部分插入文件。
我已经尝试为 BULK INSERT 命令的 CODEPAGE 参数使用各种值,但没有一个能改善这种情况,而且确实有效果的值会导致批量插入失败。例如,使用 CODEPAGE = 1200(这是 UTF-16 的代码页)会导致以下错误:
Msg 4864, Level 16, State 1, Line 1
Bulk load data conversion error (type mismatch or invalid character for the
specified codepage) for row 1, column 1 (export_date).