哈萨克语不区分大小写排序规则中的字符问题

Den*_*kin 2 sql-server collation

我对 \xd0\xa1yrillic 字符 'E' 和 'e' 有问题,当使用不区分大小写的排序规则时,它们应该相等。这对于除哈萨克语之外的所有校对都是正确的。

\n

我使用下面的查询检查了所有 CI 排序规则:

\n
SELECT 'DECLARE @Test_' + name + ' TABLE (\n    Code nvarchar(32) COLLATE ' + name + '\n)\n\nINSERT @Test_' + name + ' (Code)\nVALUES (N''\xd0\x95''), (N''\xd0\xb5'')\n\nSELECT ''' + name + ''', * FROM @Test_' + name + ' WHERE Code = N''\xd0\x95'' COLLATE ' + name \n\nFROM sys.fn_helpcollations()\nWHERE name LIKE '%CI%'\n
Run Code Online (Sandbox Code Playgroud)\n

对于所有排序规则,都会按预期返回两行,但哈萨克语除外。

\n

用于说明问题的示例查询:

\n
SET NOCOUNT ON;\n\nDECLARE @Test TABLE (\n    Code nvarchar(32) COLLATE Kazakh_90_CI_AS\n);\n\nDECLARE @UpperChar  nchar(1) = N'\xd0\x95';\nDECLARE @LowerChar  nchar(1) = N'\xd0\xb5';\n\nSELECT  ASCII(@UpperChar) AS 'UpperChar ASCII', ASCII(@LowerChar) AS 'LowerChar ASCII';\n\n/* Just ASCII-codes for the chars\n\nUpperChar ASCII LowerChar ASCII\n--------------- ---------------\n197             229\n*/\n\nINSERT @Test (Code)\nVALUES (@UpperChar), (@LowerChar);\n\nSELECT DISTINCT Code AS 'DISTINCT Code' FROM @Test;\n\n/* Should be one row with CI collation - FALSE\n\nDISTINCT Code\n--------------------------------\n\xd0\x95\n\xd0\xb5\n*/\n\nSELECT Code AS 'Code = @UpperChar'\nFROM @Test\nWHERE Code = @UpperChar;\n\n/* Should be two rows with CI collation - FALSE\n\nCode = @UpperChar\n--------------------------------\n\xd0\x95\n*/\n\nSELECT Code AS 'Code = @LowerChar'\nFROM @Test \nWHERE Code = @LowerChar;\n\n/* Should be two rows with CI collation - FALSE\n\nCode = @LowerChar\n--------------------------------\n\xd0\xb5\n*/\n\nSELECT Code AS 'Code = @UpperChar OR Code = LOWER(@UpperChar)'\nFROM @Test\nWHERE Code = @UpperChar\n    OR Code = LOWER(@UpperChar);\n\n/*Check LOWER('\xd0\x95') = '\xd0\xb5'  - TRUE\n\nCode = @UpperChar OR Code = LOWER(@UpperChar)\n---------------------------------------------\n\xd0\x95\n\xd0\xb5\n*/\n\nSELECT Code AS 'Code = @LowerChar OR Code = UPPER(@LowerChar)'\nFROM @Test \nWHERE Code = @LowerChar\n    OR Code = UPPER(@LowerChar);\n\n/*Check UPPER('\xd0\xb5') = '\xd0\x95' - TRUE\n\nCode = @LowerChar OR Code = UPPER(@LowerChar)\n---------------------------------------------\n\xd0\x95\n\xd0\xb5\n*/\n
Run Code Online (Sandbox Code Playgroud)\n

其他 \xd0\xa1yrillic 字符的行为与预期一致。

\n

我可以执行什么操作来解决该问题?

\n

Sol*_*zky 6

在我们讨论具体细节之前,这里有两件事通常会有所帮助:

\n
    \n
  • ASCII()函数适用于VARCHAR数据,并且对与数据排序规则关联的代码页敏感(对于变量,它是当前数据库的默认排序规则)。但在本例中,我们只处理 Unicode/NVARCHAR数据,因此UNICODE()应该使用该函数。
  • \n
  • CHAR()当处理 ASCII 值/代码点高于 127 的字符时,让脚本使用或函数创建特定字符会很有帮助NCHAR()。这使得脚本更易于传输,因为在不支持某些字符的环境中打开/粘贴脚本时不会出现字符转换问题。而且,它使脚本更具可读性/易于理解,因为在处理看起来像其他字符但实际上不同的字符时读者不会感到困惑(例如我们在这里处理的字符)。
  • \n
\n

现在,首先介绍一点背景知识,以便解释更有意义:

\n

Unicode 排序/比较是通过为每个字符分配多个权重来完成的。其中两个权重类别是大小写和变音符号(即重音符号)。拥有多个类别可以更轻松地处理区分大小写与不敏感以及区分重音与不敏感的各种组合。大多数(如果不是全部)定义的字符都有默认的排序权重。当使用特定的区域性/区域设置时,这些默认权重可以被特定于区域性的值覆盖。使用美国英语时,将使用默认值(即不覆盖)。这就是为什么即使使用排序规则,来自其他语言的字符仍然经常正确排序(或大部分正确)Latin1_General,以及为什么美国英语在使用Hebrew(或Japanese等)排序规则时仍然可以正确工作(因为Hebrew(因为排序规则不会覆盖美国英语)英文字符的粗细)。

\n

每个角色的各种权重都保存在一个文件中。大写和小写映射位于单独的文件中。而且,虽然 Unicode 联盟已经进入了每年更新的节奏,但微软迄今为止的更新频率却较低。根据他们公开的排序权重文件,他们只有以下版本(适用于Windows):

\n
    \n
  1. Windows NT 4.0 到 Windows Server 2003
  2. \n
  3. 视窗Vista
  4. \n
  5. Windows Server 2008
  6. \n
  7. Windows 7 和 Windows Server 2008 R2
  8. \n
  9. Windows 8 和 Windows Server 2012
  10. \n
  11. Windows 10
  12. \n
\n

SQL Server 的版本较少:

\n
    \n
  1. 名称中没有版本号的排序规则是版本“80”(即在 SQL Server 2000 中引入)
  2. \n
  3. _90_SQL Server 2005 中引入了名称中的排序规则
  4. \n
  5. _100_SQL Server 2008 中引入了名称中的排序规则
  6. \n
  7. SQL Server 2017 中引入了名称中的排序规则_140_(遗憾的是,此版本唯一的排​​序规则是日语排序规则)
  8. \n
\n

最后,请记住,SQL Server 排序规则基于Windows 排序规则,但并不完全相同。我认为版本 100 排序规则与“Windows Server 2008”文件关联,而版本 80 和 90 排序规则应该与“Windows NT 4.0 到 Windows Server 2003”文件关联最紧密(因为 Vista 于 2007 年发布) 。

\n

考虑到所有这些:

\n

这样一来,阅读本文的任何人都不会感到困惑:这里有问题的字符是“西里尔大写字母 Ie:\xd0\x95”和“西里尔小写字母 Ie:\xd0\xb5”(Unicode 代码点 U+0415 和U+0435,分别),看起来与拉丁字符“E”和“e”相同,但绝对不同。例如:

\n
SELECT  NCHAR(0x0415) AS [Cyrillic Capital Letter Ie],\n        NCHAR(0x0045) AS [Latin Capital Letter E],\n        IIF(NCHAR(0x0415) = NCHAR(0x0045) COLLATE Kazakh_100_CI_AI,\n            \'=\', \'<>\') AS [Kazakh],\n        IIF(NCHAR(0x0415) = NCHAR(0x0045) COLLATE Latin1_General_100_CI_AI,\n            \'=\', \'<>\') AS [Latin1_General]\n
Run Code Online (Sandbox Code Playgroud)\n

返回:

\n
Cyrillic Capital Letter Ie    Latin Capital Letter E    Kazakh    Latin1_General\n\xd0\x95                             E                         <>        <>\n
Run Code Online (Sandbox Code Playgroud)\n

所有 Microsoft 排序权重文件的默认行为是这两个字符除了大小写之外都是相同的。这就是为什么它们在所有不区分大小写的排序规则中比较为相等,除了Kazakh

\n

使用不区分大小写的哈萨克语排序规则时,为什么这两个字符 \xe2\x80\x94 U+0415 和 U+0435 \xe2\x80\x94 比较不同?因为(无论出于何种原因),“Windows NT 4.0 到 Windows Server 2003”和“Windows Server 2008”排序权重文件都包含 U+0435 的覆盖(“西里尔小写字母 Ie:\xd0\xb5”)使用哈萨克文化。覆盖使该字符 U+0435 等于以下字符(以及其他一些字符):

\n
Cyrillic Capital Letter Ie    Latin Capital Letter E    Kazakh    Latin1_General\n\xd0\x95                             E                         <>        <>\n
Run Code Online (Sandbox Code Playgroud)\n

返回:

\n
-- case-sensitive\nSELECT  NCHAR(0x0435) AS [Cyrillic Small Letter Ie], -- \xd0\xb5\n        NCHAR(0x04bf) AS [Cyrillic Small Letter Abkhasian Che With Descender], -- \xd2\xbf\n        IIF(NCHAR(0x0435) = NCHAR(0x04bf) COLLATE Kazakh_90_CS_AI, \'=\', \'<>\') AS [Kazakh_90], -- =\n        IIF(NCHAR(0x0435) = NCHAR(0x04bf) COLLATE Kazakh_100_CS_AI, \'=\', \'<>\') AS [Kazakh_100], -- =\n        IIF(NCHAR(0x0435) = NCHAR(0x04bf) COLLATE Latin1_General_CS_AI, \'=\', \'<>\') AS [Latin1_General], -- =\n        IIF(NCHAR(0x0435) = NCHAR(0x04bf) COLLATE Latin1_General_100_CS_AI, \'=\', \'<>\') AS [Latin1_General_100] -- <>\n\n-- accent-sensitive\nSELECT  NCHAR(0x0435) AS [Cyrillic Small Letter Ie], -- \xd0\xb5\n        NCHAR(0x0404) AS [Cyrillic Capital Letter E], -- \xd0\x84\n        IIF(NCHAR(0x0435) = NCHAR(0x0404) COLLATE Kazakh_90_CI_AS, \'=\', \'<>\') AS [Kazakh_90],-- <>\n        IIF(NCHAR(0x0435) = NCHAR(0x0404) COLLATE Kazakh_100_CI_AS, \'=\', \'<>\') AS [Kazakh_100], -- =\n        IIF(NCHAR(0x0435) = NCHAR(0x0404) COLLATE Latin1_General_CI_AS, \'=\', \'<>\') AS [Latin1_General],-- <>\n        IIF(NCHAR(0x0435) = NCHAR(0x0404) COLLATE Latin1_General_100_CI_AS, \'=\', \'<>\') AS [Latin1_General_100]-- <>\n\n-- case and accent -sensitive\nSELECT  NCHAR(0x0435) AS [Cyrillic Small Letter Ie], -- \xd0\xb5\n        NCHAR(0x0454) AS [Cyrillic Small Letter E], -- \xd1\x94\n        IIF(NCHAR(0x0435) = NCHAR(0x0454) COLLATE Kazakh_90_CS_AS, \'=\', \'<>\') AS [Kazakh_90], -- <>\n        IIF(NCHAR(0x0435) = NCHAR(0x0454) COLLATE Kazakh_100_CS_AS, \'=\', \'<>\') AS [Kazakh_100], -- =\n        IIF(NCHAR(0x0435) = NCHAR(0x0454) COLLATE Latin1_General_CS_AS, \'=\', \'<>\') AS [Latin1_General], -- <>\n        IIF(NCHAR(0x0435) = NCHAR(0x0454) COLLATE Latin1_General_100_CS_AS, \'=\', \'<>\') AS [Latin1_General_100] -- <>\n
Run Code Online (Sandbox Code Playgroud)\n

哈萨克语特定的覆盖还会导致字符“西里尔小写字母 Io”(U+0451) 不再等同于其大写字符:

\n
Cyrillic Small  Cyrillic Small Letter         Kazakh  Kazakh  Latin1_  Latin1_\nLetter Ie       Abkhasian Che With Descender  _90     _100    General  General_100\n\xd0\xb5               \xd2\xbf                             =       =       =        <>\n\n\nCyrillic Small    Cyrillic Capital    Kazakh    Kazakh    Latin1_    Latin1_\nLetter Ie         Letter E            _90       _100      General    General_100\n\xd0\xb5                 \xd0\x84                   <>        =         <>         <>\n\n\nCyrillic Small    Cyrillic Small    Kazakh    Kazakh    Latin1_    Latin1_\nLetter Ie         Letter E          _90       _100      General    General_100\n\xd0\xb5                 \xd1\x94                 <>        =         <>         <>\n
Run Code Online (Sandbox Code Playgroud)\n

返回:

\n
-- Also messed up, just like Ie; accent-sensitive\nSELECT  NCHAR(0x0451) AS [Cyrillic Small Letter Io], -- \xd1\x91\n        NCHAR(0x0401) AS [Cyrillic Capital Letter Io], -- \xd0\x81\n        IIF(NCHAR(0x0451) = NCHAR(0x0401) COLLATE Kazakh_90_CI_AS, \'=\', \'<>\') AS [Kazakh_90],-- <>\n        IIF(NCHAR(0x0451) = NCHAR(0x0401) COLLATE Kazakh_100_CI_AS, \'=\', \'<>\') AS [Kazakh_100], -- <>\n        IIF(NCHAR(0x0451) = NCHAR(0x0401) COLLATE Latin1_General_CI_AS, \'=\', \'<>\') AS [Latin1_General],-- =\n        IIF(NCHAR(0x0451) = NCHAR(0x0401) COLLATE Latin1_General_100_CI_AS, \'=\', \'<>\') AS [Latin1_General_100]-- =\n
Run Code Online (Sandbox Code Playgroud)\n

所有这些行为都是 Microsoft 最初实现 Unicode 的残余(早在 Windows NT 4.0 中!)。值得赞扬的是,Microsoft 是 Unicode 的早期采用者,这种行为可能就是 Unicode 1.0 版中定义的方式。很难确定,因为找到 Unicode 的原始排序文件并不容易(我认为 2.1 版本是我能找到的最早的版本)。但是,通过查看 Microsoft 提供的文件,我可以说,尽管他们更新了默认排序权重并在每个文件中添加了字符和文化,但一些定义(例如哈萨克语特定的覆盖)直到最近才更新。事实上,直到“Windows 8 和 Windows Server 2012”文件(即第二组最新的定义),他们才修复了哈萨克语特定的覆盖(我假设还有其他覆盖)。

\n

因此,尽管 Windows(可能还有 .NET)通常可以正确处理哈萨克语排序规则(从 Windows 8 和 Windows Server 2012 开始),但 SQL Server 排序规则仍然停留在过去。这意味着,除了 Microsoft 将这些排序规则更新为更新版本的 Unicode 之外,没有其他解决办法。我确实有一个想法,我一直在考虑向他们建议如何使其比简单的定义更新更好,所以也许我应该继续提交(一旦我有了链接,我将用链接更新这个答案)做到了)。

\n

但现在,如果您确实需要这两个字符在不区分大小写的哈萨克语排序规则中相等(并且不要忘记“西里尔小写字母 Io”(U+0451)),则必须执行以下操作UPPER()您的例如(尽管我不能肯定地说这种解决方法不会导致任何问题)。

\n
\n

有关排序规则、Unicode 和编码的更多信息,请访问我的网站:Collat​​ions.Info

\n