该场景是一个 SQL Server 实例,一个主要使用 BULK INSERT 操作提供数据的数据库,并且插入的一些文本包含特殊字符,例如ñ
因为我在西班牙环境中工作。
所以,在最初的小测试之后,我意识到当我运行一个简单的 时这些特殊字符没有正确显示select
,所以我开始检查我能想到的一切:
- 文件编码:要批量插入的文件具有正确的编码:ANSI
- 数据库编码:数据库具有正确的编码(谢天谢地):
select collation_name from sys.databases where name='DBNAME';
结果SQL_Latin1_General_CP1_CI_AS
- Latin1:使用的字符集。这很适合我
- 将军:这里没什么有趣的
- CP1:这意味着它使用代码页 1,简而言之,这意味着代码页 1252 <=> 用于编码 WIN-1252 的代码页,与 Latin1 非常相似
- CI:不区分大小写
- AS:对口音敏感,所以 á 与 a 不同
- 将数据导出到文件并检查:文件编码为ANSI ,但数据显示不正确,没有特殊字符,而是我发现了一些其他字符,使文本难以阅读。
通过这些测试,我得出的结论是数据没有正确存储,这就是它没有正确显示和导出的原因。我在互联网上找到的几乎所有解决方案都建议使用字符串数据类型字段nvarchar
而不是varchar
字符串数据类型字段,但这并不能解决这种情况。是什么破坏了我的插入?
如果字符导入不正确,则以下一个(或两个)区域存在问题,因为这实际上是一个两步过程:
BCP / BULK INSERT 不知道文件是如何编码的并且解释不正确
目标列是
VARCHAR
(或CHAR
{orTEXT
,但不要使用TEXT
} )并且该目标列(不是数据库)的排序规则使用的代码页没有错误导入字符的映射。这里需要注意的是,导入表所在的数据库的默认排序规则是不相关的。这里唯一重要的排序规则是要导入的每个特定字符串列的排序规则。并且每个字符串列的排序规则都可以不同,它们都不需要与数据库的默认排序规则相同。通常让数据库中大多数列的排序规则与数据库的默认值匹配,因为这将是创建新表和列时使用的排序规则,而不是通过
COLLATE
关键字指定排序规则。第 1 步:文件编码
BCP / BULK INSERT(或大多数其他读取文件的代码)将不知道文件正在使用什么编码,除非文件使用为数不多的具有字节顺序标记 (BOM)的编码之一。但是扩展的 ASCII 编码不使用字节顺序标记,因此不能以编程方式确定(至少不能确定)。使用扩展 ASCII 编码时,您需要指定代码页,否则将假定为默认值。
根据bcp Utility的 MSDN 页面,在-C选项下:
您可以通过打开命令提示符并运行
mode
or来确定默认代码页chcp
(我更喜欢chcp
它,因为如果您传入一个值,它还允许更改代码页)。如果您的默认代码页是 850,但文件是使用 Windows-1252 Latin1 (ANSI) 的编码保存的,那么解释文件很容易出现问题,因为这两个代码页之间的字符映射不同。这与 SQL Server 没有任何关系。
该
ñ
字符在代码页 1252 上的值为 241。但是,在代码页 850 上,同一字符的值为 164。文件,不管其他任何内容,都是一系列字节,其中一个字节具有十进制值241(因为当它被保存时,它被告知使用代码页 1252 确定ñ
需要存储为 241)。现在,当 BCP 读取文件时,如果它使用默认的 MS-DOS 代码页 850,则相同的字节值 241 映射到 character±
。如果您要通过开关指定一个ACP
或1252
(相同的东西)的代码页-C
,那么 BCP 将知道值 241 的字节实际上是ñ
. 或者,您可以指定代码页 1255(Windows 希伯来语),然后 BCP / BULK INSERT 会将相同的字节值 241 解释为字符ס
.第 2 步:目标列数据类型和排序规则
一旦 BCP / BULK INSERT(或任何客户端应用程序)读取数据,它就会作为这些映射存在,而不仅仅是基本字节值。读入 BCP / BULK INSERT 的任何字符都将作为该字符存储在目标列中,只要该字符可以映射到目标数据类型和排序规则中。
NVARCHAR
、NCHAR
和NTEXT
(但不要使用)的目标数据类型NTEXT
可以保存所有字符,因此 Collation 是什么并不重要。但是,如果目标数据类型是VARCHAR
,CHAR
, 或TEXT
然后排序规则将确定代码页,该代码页又确定字符映射。如果目标数据类型是最后提到的 3 种之一,并且使用与用于文件的相同代码页关联的排序规则,那么一切都应该正常工作。或者,如果 Collation 与不同的代码页相关联,则将尝试映射character,而不是 byte 值。意思是,如果 BCP / BULK INSERT 正在使用代码页 1252 和字符
ñ
(代码页 1252 上的值 241),那么如果您将其导入到VARCHAR
具有排序规则的列中SQL_Latin1_General_CP850_CI_AS
——它使用代码页 850——然后您将看到一个字符ñ
(相同的字符,但代码页 850 上的值为 164)而不是±
,它在代码页 850 上具有相同的 241 值。但是,如果您导入到VARCHAR
具有排序规则的列中Hebrew_CI_AS
-- 它使用代码页1255 - 然后您将看到?
而不是ס
(代码页 1255 上的值 241),因为在代码页 1255 上没有映射ñ
。对于那些可能会遇到与我类似的问题的人来说,对我有用的东西与代码页系统有关。我将进一步详细解释:
数据库的整理表明数据库使用了代码页 1252,在检查了我的计算机活动代码页(运行
cmd
,然后在 cmd 中mode
)后,我发现它是 850,所以一定是发生了冲突!如果计算机无法正确打印我的特殊字符,那么数据库将存储我的计算机打印到的任何愚蠢的字符串。
我添加了选项
CODEPAGE=ACP
,它强制批量插入使用 sql 服务器的代码页,一切都很好:)