Dav*_*ims 5 sql-server collation encoding unicode bulk-insert
出于某种原因,MS SQL Server 2016 批量插入会误解/翻译 Unicode 字符:
即 Notepad++ 和 xxd 显示平面文件有 0xC9,但在批量插入后,表显示“+”,并在 SQL Server 中转换为 varbinary 显示为 0x2B。备份也有 0xC9。
我正在向 MS SQL Server 2016 中批量插入 25 个平面文件。它是 15Gb 数据,我正在使用管道 ( | ) 字段分隔符和CRLF行分隔符。
我批量插入到我提供的备份的截断结构中。当我与备份进行比较时,存在差异。注意:我必须等待 25 小时才能从数据源备份,但可以在 15 分钟内获取平面文件。
有些差异是可以接受的(查找和替换我正在应用到平面文件),但许多差异是由于 Unicode 字符被误解了。
一个示例表的结构是:
CREATE TABLE [dbo].[obfuscated_name](
[ob_1] [int] NOT NULL,
[ob_2] [int] NOT NULL,
[ob_3] [int] NOT NULL,
[ob_4] [nvarchar](300) NULL
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)
数据库排序规则为默认值:SQL_Latin1_General_CP1_CI_AS。没有任何列具有不同的排序规则。此排序规则应使用代码页 1252,它应正确解释我遇到问题的字符。
我的流程正在针对不断变化的生产数据运行,所以我担心可能会弹出其他更改,我想知道问题的根源,而不是尝试隔离问题并手动更新误解。
这不是 SQL Server(甚至 Windows)中的错误,也不是需要将文件转换为另一种编码(即转换为“Unicode”,在 Windows 世界中的意思是“UTF-16 Little端”)。这只是一个简单的误传。
通信故障的来源(它总是相同的,对;-)只是在源数据的性质上没有达成一致。将字符数据从一处移动到另一处时,指定两侧的编码很重要。是的,SQL_Latin1_General_CP1_*
排序规则使用代码页 1252。但是,如果您不告诉BULK INSERT
或BCP.exe源文件的代码页是什么,那么它们将假定代码页是系统默认值。
BULK INSERT的文档甚至指出(对于CODEPAGE =
参数):
'OEM'(默认)= char、varchar或text数据类型的列从系统 OEM 代码页转换为 SQL Server 代码页。
BCP.exe的文档说明(对于-C
开关):
OEM = 客户端使用的默认代码页。如果未指定 -C,则这是使用的默认代码页。
Windows 的默认代码页是(至少对于美国英语系统):437。如果您在命令提示符中执行以下命令,您可以看到这一点:
C:\> CHCP
Run Code Online (Sandbox Code Playgroud)
它将返回:
Active code page: 437
Run Code Online (Sandbox Code Playgroud)
但是您的源文件不是使用代码页 437 编码的。它是使用代码页 1252 编码的。
所以这就是正在发生的事情:
É
使用代码页 1252 时。?
,但 BULK INSERT / BCP 不显示它,所以你不会看到这个)®
在代码页 1252 上«
)映射到代码页 1252 上的字节0xAB(因为它也显示为«
)。以下示例显示了问题中提到的所有字符的这种转换:
DECLARE @CodePageConversion TABLE
(
[ActualSource_CP1252] AS CONVERT(VARCHAR(10), CONVERT(BINARY(1),
[PerceivedSource_CP437])) COLLATE SQL_Latin1_General_CP1_CI_AS,
[PerceivedSource_CP437] VARCHAR(10) COLLATE SQL_Latin1_General_CP437_CI_AS,
[Source_Value] AS (CONVERT(BINARY(1), [PerceivedSource_CP437])),
[Destination_CP1252] AS (CONVERT(VARCHAR(10), [PerceivedSource_CP437]
COLLATE SQL_Latin1_General_CP1_CI_AS)),
[CP1252_Value] AS (CONVERT(BINARY(1), CONVERT(VARCHAR(10),
[PerceivedSource_CP437] COLLATE SQL_Latin1_General_CP1_CI_AS)))
);
INSERT INTO @CodePageConversion
VALUES (0xC9), (0xA1), (0xA0), (0xAE), (0xCB), (0xD1), (0x92), (0x96);
SELECT * FROM @CodePageConversion;
Run Code Online (Sandbox Code Playgroud)
返回:
ActualSource_CP1252 PerceivedSource_CP437 Source_Value Destination_CP1252 CP1252_Value
É ? 0xC9 + 0x2B
¡ í 0xA1 í 0xED
á 0xA0 á 0xE1
® « 0xAE « 0xAB
Ë ? 0xCB - 0x2D
Ñ ? 0xD1 - 0x2D
’ Æ 0x92 Æ 0xC6
– û 0x96 û 0xFB
Run Code Online (Sandbox Code Playgroud)
代码页 1252 中不存在 0xC9、0XCB 和 0xD1 的字符,因此使用了“最佳拟合”映射,这就是转换后最终得到+
和-
字符的原因。
此外,即使目标列正在使用NVARCHAR
,所有这些映射都是相同的,因此您会看到完全相同的行为。
所以,你的选择是:
如果使用 T-SQLBULK INSERT
命令,请使用WITH CODEPAGE =
以下值之一指定选项:
'ACP'
(这与 相同'1252'
)'RAW'
(如果插入到VARCHAR
,则使用列的排序规则的代码页,或者'OEM'
在插入到时与/ 代码页 437 相同NVARCHAR
)'1252'
(这与 相同'ACP'
)或者,如果使用BCP.exe,则指示传入文件通过-C
命令行开关使用代码页 1252以及以下值之一(请参阅选项 #1 中的注释):
ACP
RAW
1252
请注意:
BULK INSERT
,插入到VARCHAR
柱,使用该组中的问题指出的字符,和ACP
(我认为代表甲NSI Ç ODE P年龄),RAW
和1252
值的所有产生正确的结果。WITH CODEPAGE =
产生的结果与 OP 在问题中报告的结果相同。这与指定WITH CODEPAGE = 'OEM'
.NVARCHAR
列中,两个ACP
并1252
如所期望的工作,但RAW
所产生的相同的结果OEM
(即,使用代码页437,而不是代码页1252由所述列的排序规则指定)。-C
开关并没有使用过程中的代码页。意思是,使用CHCP更改命令提示符的代码页无效:代码页 437 仍用作源代码页。PS由于这里的数据都是8位编码的,所以没有“Unicode字符”,因为没有使用Unicode。