ror*_*.ap 4 t-sql sql-server unicode collation sql-server-2008
这是我的两个变量:
DECLARE @First VARCHAR(254) = '5’-Phosphate Analogs Freedom to Operate'
DECLARE @Second NVARCHAR(254) = CONVERT(NVARCHAR(254), @First)
Run Code Online (Sandbox Code Playgroud)
我有两个数据库,我们称它们为“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
Run Code Online (Sandbox Code Playgroud)
结果是:
Equal
Equal
Run Code Online (Sandbox Code Playgroud)
然后我连接到 Database2 并运行查询;结果是:
Equal
Not Equal
Run Code Online (Sandbox Code Playgroud)
请注意,我没有更改查询本身,只是更改了 db 连接,并且我指定了要使用的排序规则,而不是允许它们使用数据库的默认排序规则。因此,我的理解是数据库默认排序规则无关紧要,即无论我连接到哪个数据库,查询的结果都应该相同。
我有三个问题:
考虑到我通过显式指定我自己的数据库排序规则有效地忽略了默认数据库排序规则,为什么当我唯一更改的是我所连接的数据库时,我会得到不同的结果?
对数据库2的测试,为什么比较通过SQL_Latin1_General_CP1_CI_AScollation成功,通过collation失败SQL_Latin1_General_CP850_CI_AS?说明此问题的两个排序规则有什么区别?
最令人困惑的是:如果我所连接的数据库的默认排序规则确实很重要,就像它看起来的那样,并且 Database1 的默认排序规则是SQL_Latin1_General_CP850_CI_AS(记住我的第一个测试结果是Equal, Equal)为什么第二个查询,哪个连接到 Database2 时显式指定完全相同的排序规则失败 ( 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];
Run Code Online (Sandbox Code Playgroud)
“当前”数据库使用与代码页 850 关联的排序规则时的结果(所有 3 个查询都返回相同的内容):
AsciiValue CodePoint
39 39
Run Code Online (Sandbox Code Playgroud)
从上面可以看出,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
Run Code Online (Sandbox Code Playgroud)
但是,如果代码页 1252 中的字符可用,为什么要从 146 转换为 8217?因为 Unicode 中的前 256 个字符不是代码页 1252,而是ISO-8859-1. 这两个代码页大多相同,但在 128 - 255 范围内有几个字符不同。在 ISO-8859-1 代码页中,这些值是控制字符。当限制已经是 256 个字符时,Microsoft 认为最好不要在不可打印的控制字符上浪费 16 个(或很多)字符。因此,他们将控制字符替换为更可用的字符,因此代码页为 1252。但 Unicode 组使用标准化的 ISO-8859-1 作为前 256 个字符。
为什么这很重要?因为您正在测试的字符是代码页 1252 中但不在ISO-8859-1中的少数幸运字符之一,因此它不能保持146转换为 NVARCHAR 时的状态,并被转换为其 Unicode 值,即8217. 您可以通过运行以下命令来查看此行为:
SELECT '~' + CHAR(146) + '~', N'~' + NCHAR(146) + N'~';
-- ~’~ ~~
Run Code Online (Sandbox Code Playgroud)
上面显示的所有内容都解释了大多数观察到的行为,但没有解释为什么,@First并且@Second当指定为COLLATE SQL_Latin1_General_CP850_CI_AS但在具有与代码页 1252 关联的默认排序规则的数据库中运行时,注册为“不相等”。如果使用 Code Page 850 将它们转换为 ASCII 39,它们应该仍然相等,对吗?
这是由于事件的顺序以及代码页与 Unicode 数据(即存储在NCHAR、 中的任何内容NVARCHAR以及NTEXT任何人都不应该使用的已弃用类型)无关的事实。分解正在发生的事情:
从@First被声明和初始化(即DECLARE @First VARCHAR(1) = '’';)开始。它是一种VARCHAR类型,因此使用代码页,因此使用与“当前”数据库的默认排序规则相关联的代码页。
“当前”数据库的默认排序规则与代码页 1252 相关联,因此该值不会转换为 ASCII 39,而是作为 ASCII 146 愉快地存在。
Next@Second被声明和初始化(即DECLARE @Second NVARCHAR(1) = @First;——不需要显式,CONVERT因为这不是生产代码,它将被隐式转换)。NVARCHAR正如我们所见,这是一种具有字符的类型,但将值从 ASCII 146 转换为代码点 U+2019(十进制 8217 = 0x2019)。
在比较,使用@First COLLATE SQL_Latin1_General_CP850_CI_AS开始与ASCII 146@First是VARCHAR使用代码页数据指定的“当前”数据库的默认排序规则。但是,由于该字符在代码页 850 中不存在(由COLLATE子句中使用的排序规则指定),它被转换为 ASCII 39(如我们上面所见)。
为什么不@Second COLLATE SQL_Latin1_General_CP850_CI_AS将该字符转换为 ASCII 39 以便它们注册为“相等”?因为:
@Second是NVARCHAR并且不使用代码页,因为所有字符都以单个字符集(即 Unicode)表示。因此,更改排序规则只能更改控制如何比较和排序字符的规则,但不会更改字符,例如更改 VARCHAR 数据的排序规则时有时会发生的情况(例如在这种情况下’)。因此,这一方面的比较仍然是 Code Point U+2019。@First,beingVARCHAR将被隐式转换为NVARCHAR用于比较。但是,该’字符已被该COLLATE SQL_Latin1_General_CP850_CI_AS子句转换为 ASCII 39 ,并且 ASCII 39 在 Unicode 中的同一位置找到,要么是 Decimal 39,要么是代码点 U+0027(来自SELECT CONVERT(BINARY(2), 39))。
结果比较介于:代码点 U+2019和代码点 U+0027
Ergo:不相等
有关使用排序规则、编码、Unicode 等的更多信息,请访问:排序规则信息