比较 VARCHAR 和 NVARCHAR 中的相同字符 CP1/CP1252 与基于 DB 排序规则的 CP850 不同

Comparing the same character in VARCHAR and NVARCHAR differs between CP1/CP1252 vs. CP850 based on DB collation

这是我的两个变量:

DECLARE @First VARCHAR(254) = '5’-Phosphate Analogs Freedom to Operate'
DECLARE @Second NVARCHAR(254) = CONVERT(NVARCHAR(254), @First)

我有两个数据库,我们称它们为 "Database1" 和 "Database2"。 Database1 的默认排序规则为 SQL_Latin1_General_CP850_CI_AS;数据库 2 是 SQL_Latin1_General_CP1_CI_AS。两个数据库的兼容级别都是 SQL Server 2008 (100).

我首先连接到 Database1 并 运行 以下查询:

SELECT CASE 
    WHEN @First COLLATE SQL_Latin1_General_CP1_CI_AS 
    = @Second COLLATE SQL_Latin1_General_CP1_CI_AS 
    THEN 'Equal' ELSE 'Not Equal' END

SELECT CASE 
    WHEN @First COLLATE SQL_Latin1_General_CP850_CI_AS 
    = @Second COLLATE SQL_Latin1_General_CP850_CI_AS 
    THEN 'Equal' ELSE 'Not Equal' END

结果是:

Equal
Equal

然后我连接到 Database2 并 运行 查询;结果是:

Equal
Not Equal

请注意,我没有更改查询本身,只是更改了数据库连接,并且我指定了要使用的排序规则,而不是允许它们使用数据库的默认排序规则。因此,我的理解是数据库默认排序规则无关紧要,即无论我连接到哪个数据库,查询结果都应该相同。

我有三个问题:

  1. 当我唯一改变的是我所连接的数据库时,为什么我会得到不同的结果,因为我已经通过显式指定我自己的数据库有效地忽略了默认数据库排序规则?

  2. 针对数据库 2 的测试,为什么 SQL_Latin1_General_CP1_CI_AS 排序规则比较成功而 SQL_Latin1_General_CP850_CI_AS 排序规则比较失败?解释这一点的两种归类之间有什么区别?

  3. 最令人困惑:如果我连接的数据库的默认排序规则确实重要,因为看起来,Database1 的默认排序规则是 SQL_Latin1_General_CP850_CI_AS(记得我的第一次测试结果是 EqualEqual)为什么第二个查询是 当连接到 Database2?

  4. 时明确指定相同的排序规则 失败 (Not Equal)

仅仅是因为这就是非 Unicode 数据的工作方式。非 Unicode 数据(即 8 位扩展 ASCII)根据代码页对前 128 个值使用相同的字符,但对第二组 128 个字符使用不同的字符。您正在测试的字符 — — 存在于代码页 1252 中,但不存在于代码页 850 中。

是的,“当前”数据库的默认排序规则对于字符串文字和局部变量绝对重要。当您在具有使用代码页 850 的默认排序规则的数据库中时,该非 Unicode 字符串文字(即 而非 前缀为 N 的字符串)会自动转换值转换为代码页 850 中确实存在的等效字符。但是,该字符确实存在于代码页 1252 中,因此无需转换它。

那么为什么在数据库中使用与非 Unicode 字符串和 Unicode 字符串之间的 Cod Page 1252 关联的排序规则时“不相等”?因为在将非 Unicode 字符串转换为 Unicode 时,会发生另一个转换,将字符转换为其真正的 Unicode 值,该值高于十进制值 256。

运行 在两个数据库中输入以下内容,您将看到会发生什么:

SELECT ASCII('’') AS [AsciiValue], UNICODE('’') AS [CodePoint];

SELECT ASCII('’' COLLATE SQL_Latin1_General_CP1_CI_AS) AS [AsciiValue],
        UNICODE('’' COLLATE SQL_Latin1_General_CP1_CI_AS) AS [CodePoint];

SELECT ASCII('’' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [AsciiValue],
        UNICODE('’' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [CodePoint];

“当前”数据库使用与代码页 850 关联的排序规则时的结果(所有 3 个查询 return 同一件事):

AsciiValue    CodePoint
39            39

正如您从上面看到的,在字符串文字上指定 COLLATE 已经根据默认值解释该字符串的事实之后“当前”数据库的整理。

“当前”数据库使用与代码页 1252 关联的排序规则时的结果:

-- no COLLATE clause
AsciiValue    CodePoint
146           8217

-- COLLATE SQL_Latin1_General_CP1_CI_AS
AsciiValue    CodePoint
146           8217

-- COLLATE SQL_Latin1_General_CP850_CI_AS
AsciiValue    CodePoint
39            39

但是如果字符在代码页 1252 中可用,为什么要从 146 转换为 8217?因为 Unicode 中的前 256 个字符 不是 代码页 1252,而是 ISO-8859-1。这两个代码页大部分相同,但在 128 - 255 范围内有几个字符不同。在 ISO-8859-1 代码页中,这些值是控制字符。当限制已经是 256 个字符时,Microsoft 认为最好不要在不可打印的控制字符上浪费 16 个(或许多)字符。因此,他们将控制字符换成了更有用的字符,因此代码页 1252。但是 Unicode 组对前 256 个字符使用了标准化的 ISO-8859-1。

为什么这很重要?因为您正在测试的字符是代码页 1252 中的少数幸运字符之一,但在 ISO-8859-1 中 不是 ,因此它在转换时不能保持为 146到 NVARCHAR,并转换为其 Unicode 值,即 8217。您可以通过以下 运行 查看此行为:

SELECT '~' + CHAR(146) + '~', N'~' + NCHAR(146) + N'~';
-- ~’~  ~~

上面显示的所有内容都解释了大部分观察到的行为,但没有解释为什么 @First@Second,当指定为 COLLATE SQL_Latin1_General_CP850_CI_AS 而 运行 在具有与代码页 1252 关联的默认排序规则,注册为“不等于”。如果使用代码页 850 将它们转换为 ASCII 39,它们应该仍然相等,对吗?

这是由于事件的顺序和代码页与 Unicode 数据(即存储在 NCHARNVARCHAR 和已弃用的 NTEXT 没有人应该使用的类型)。分解正在发生的事情:

  1. @First 开始声明和初始化(即 DECLARE @First VARCHAR(1) = '’';)。它是 VARCHAR 类型,因此使用代码页,因此使用与“当前”数据库的默认排序规则关联的代码页。

  2. “当前”数据库的默认排序规则与代码页 1252 关联,因此该值 转换为 ASCII 39,但作为ASCII 146.

  3. 接下来声明并初始化 @Second(即 DECLARE @Second NVARCHAR(1) = @First;——不需要显式 CONVERT,因为这不是生产代码,它将被隐式转换).这是一个 NVARCHAR 类型,正如我们所见,它具有字符,但将值从 ASCII 146 转换为代码点 U+2019(十进制 8217 = 0x2019)。

  4. 在比较中,使用@First COLLATE SQL_Latin1_General_CP850_CI_AS以ASCII 146开头,因为@FirstVARCHAR使用“当前”的默认排序规则指定的代码页的数据“数据库。但是,由于代码页 850 中不存在该字符(由 COLLATE 子句中使用的排序规则指定),因此它被翻译成 ASCII 39(如我们在上面看到的)。

  5. 为什么 @Second COLLATE SQL_Latin1_General_CP850_CI_AS 不将该字符也转换为 ASCII 39,以便它们注册为“相等”?因为:

    • @SecondNVARCHAR 并且不使用代码页,因为所有字符都以单个字符集(即 Unicode)表示。因此,更改 Collat​​ion 只能更改控制如何比较和排序字符的规则,但不会更改字符,例如更改 VARCHAR 数据的 Collat​​ion 时有时会发生的情况(例如 的这种情况)。因此比较的这边还是Code Point U+2019.
    • @FirstVARCHAR 将隐式转换为 NVARCHAR 进行比较。但是, 字符已经被 COLLATE SQL_Latin1_General_CP850_CI_AS 子句翻译成 ASCII 39,并且 ASCII 39 在同一位置的 Unicode 中找到,作为十进制 39 或代码点 U+0027(来自 SELECT CONVERT(BINARY(2), 39)).


结果比较是:代码点 U+2019代码点 U+0027
因此:不等于


有关使用排序规则、编码、Unicode 等的更多信息,请访问:Collations Info