为什么搜索 LIKE N'%?%' 匹配任何 Unicode 字符并且 = N'?' 匹配很多?

Mar*_*ith 26 sql-server like unicode sql-server-2016

DECLARE @T TABLE(
  Col NCHAR(1));

INSERT INTO @T
VALUES      (N'A'),
            (N'B'),
            (N'C'),
            (N'?'),
            (N'?'),
            (N'?');
Run Code Online (Sandbox Code Playgroud)
SELECT *
FROM   @T
WHERE  Col LIKE N'%?%'
Run Code Online (Sandbox Code Playgroud)

退货

Col
A
B
C
?
?
?
Run Code Online (Sandbox Code Playgroud)
SELECT *
FROM   @T
WHERE  Col = N'?' 
Run Code Online (Sandbox Code Playgroud)

退货

Col
?
?
?
Run Code Online (Sandbox Code Playgroud)

使用下面的生成每个可能的双字节“字符”显示=版本匹配其中的 21,229 个和LIKE N'%?%'所有版本(我尝试了一些非二进制排序规则,结果相同)。

WITH T(I, N)
AS 
(
SELECT TOP 65536 ROW_NUMBER() OVER (ORDER BY @@SPID),
                 NCHAR(ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM master..spt_values v1, 
     master..spt_values v2
)
SELECT I, N 
FROM T
WHERE N = N'?'  
Run Code Online (Sandbox Code Playgroud)

任何人都能够阐明这里发生了什么?

使用COLLATE Latin1_General_BINthen 匹配单个字符NCHAR(65533)- 但问题是要了解它在其他情况下使用的规则。这 21,229 个与 匹配的字符有什么特别之处=,为什么所有内容都与通配符匹配?我想我失踪的背后有某种原因。

nchar(65534)[和其他 21,000] 工作与nchar(65533). 这个问题可以用nchar(502) 同样的方式来表达?- 它的行为与LIKE N'%?%'(匹配所有内容)和=案例中的行为相同。这大概是一个很大的线索。

SELECT最后一个查询中的更改为SELECT I, N, RANK() OVER(ORDER BY N)表明 SQL Server 无法对字符进行排名。似乎没有由排序规则处理的任何字符都被认为是等效的。

具有Latin1_General_100_CS_AS排序规则的数据库产生 5840 个匹配项。大大Latin1_General_100_CS_AS减少了=比赛,但不会改变LIKE行为。看起来确实有一堆字符在后来的排序规则中变小了,这些字符都比较相等,LIKE然后在通配符搜索中被忽略。

我使用的是 SQL Server 2016。符号?是 Unicode 替换字符,但 UCS-2 编码中唯一的无效字符是 55296 - 57343 AFAIK,它显然匹配完全有效的代码点,例如N'?'不在此范围内的代码点。

这些特点表现得就像一个空字符串LIKE=。他们甚至评估为等效。N'' = N'?'是真的,你可以把它放在LIKE单个空格的比较中LIKE '_' + nchar(65533) + '_'而不会有任何影响。LEN比较会产生不同的结果,所以它可能只是某些字符串函数。

我认为这种LIKE行为是正确的;它的行为就像一个未知值(可以是任何值)。这些其他字符也会发生这种情况:

  • nchar(11217) (不确定性标志)
  • nchar(65532) (对象替换字符)
  • nchar(65533) (替换字符)
  • nchar(65534) (不是字符)

因此,如果我想找到所有用等号表示不确定性的字符,我将使用支持补充字符的排序规则,例如Latin1_General_100_CI_AS_SC.

我猜这些是文档Collat​​ion 和 Unicode Support 中提到的“非加权字符”组。

Sol*_*zky 13

一个“字符”(可以由多个代码点组成:代理对、组合字符等)与另一个“字符”的比较基于一组相当复杂的规则。由于需要考虑Unicode规范中表示的所有语言中的所有各种(有时是“古怪的”)规则,因此它非常复杂。此系统适用于所有NVARCHAR数据的非二进制排序规则,以及VARCHAR使用 Windows 排序规则而非 SQL Server 排序规则(以 开头的数据SQL_)。该系统不适用于VARCHAR使用 SQL Server 排序规则的数据,因为这些数据使用简单映射。

大多数规则在Unicode Collat​​ion Algorithm (UCA)中定义。其中一些规则以及 UCA 未涵盖的一些规则是:

  1. allkeys.txt文件中给出的默认排序/重量(如下所述)
  2. 正在使用哪些敏感度和选项(例如,它是区分大小写还是不区分大小写?如果敏感,那么是先大写还是先小写?)
  3. 任何基于语言环境的覆盖。
  4. 正在使用 Unicode 标准的版本。
  5. “人为”因素(即 Unicode 是一种规范,而不是软件,因此由每个供应商来实现它)

我强调了关于人为因素的最后一点,希望明确指出不应期望 SQL Server 始终按照规范 100% 运行。

这里最重要的因素是赋予每个代码点的权重,以及多个代码点可以共享相同的权重规范这一事实。您可以在此处找到基本权重(没有特定于语言环境的覆盖)(我相信100排序规则系列是 Unicode v 5.0——在Microsoft Connect 项目的评论中非正式确认):

http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt

有问题的代码点——U+FFFD——定义为:

FFFD  ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER
Run Code Online (Sandbox Code Playgroud)

该符号在 UCA 的第9.1Allkeys 文件格式中定义:

<entry>       := <charList> ';' <collElement>+ <eol>
<charList>    := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt>         := "*" | "."

Collation elements marked with a "*" are variable.
Run Code Online (Sandbox Code Playgroud)

最后一行很重要,因为我们正在查看的代码点有一个确实以“*”开头的规范。在第3.6变量权重中,根据我们无法直接访问的排序规则配置值定义了四种可能的行为(这些被硬编码到每个排序规则的 Microsoft 实现中,例如区分大小写是先使用小写还是先使用小写?大写优先,VARCHAR使用SQL_排序规则和所有其他变体的数据之间不同的属性)。

我没有时间对采用哪些路径进行全面研究,也没有时间推断正在使用哪些选项,以便提供更可靠的证据,但可以肯定地说,在每个代码点规范中,无论是否有某些东西被认为“相等”并不总是使用完整的规范。在这种情况下,我们有“0F12.0020.0002.FFFD”,很可能只是使用了级别 2 和级别 3(即.0020.0002.)。在 Notepad++ 中为“.0020.0002”做“计数”。找到 12,581 个匹配项(包括我们尚未处理的补充字符)。对“[*”进行“计数”会返回 4049 个匹配项。使用以下模式进行正则表达式“查找”/“计数”\[\*\d{4}\.0020\.0002返回 832 个匹配项。所以在这个组合中的某个地方,再加上我没有看到的其他一些规则,再加上一些 Microsoft 特定的实现细节,就是对这种行为的完整解释。需要明确的是,所有匹配字符的行为都是相同的,因为它们都相互匹配,因为一旦应用规则,它们都具有相同的权重(意思是,这个问题可能是针对其中任何一个而提出的,而不是必然先生?)。

您可以查看下面的查询,并根据查询下方COLLATE的结果更改子句,不同的敏感性如何跨两个版本的排序规则工作:

;WITH cte AS
(
  SELECT     TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS [Num]
  FROM       [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARBINARY(2), cte.Num) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM   cte
WHERE  NCHAR(cte.Num) = NCHAR(0xFFFD) COLLATE Latin1_General_100_CS_AS_WS --N'?'
ORDER BY cte.Num;
Run Code Online (Sandbox Code Playgroud)

不同排序规则中匹配字符的各种计数如下。

Latin1_General_100_CS_AS_WS   =   5840
Latin1_General_100_CS_AS      =   5841 (The "extra" character is U+3000)
Latin1_General_100_CI_AS      =   5841
Latin1_General_100_CI_AI      =   6311

Latin1_General_CS_AS_WS       = 21,229
Latin1_General_CS_AS          = 21,230
Latin1_General_CI_AS          = 21,230
Latin1_General_CI_AI          = 21,537
Run Code Online (Sandbox Code Playgroud)

在上面列出的所有排序规则中,N'' = N'?'也评估为 true。

更新

我能够做更多的研究,这是我发现的:

它“可能”应该如何工作

使用ICU Collat​​ion Demo,我将语言环境设置为“en-US-u-va-posix”,将强度设置为“primary”,检查显示“排序键”,并粘贴了我从上面查询的结果(使用Latin1_General_100_CI_AI排序规则):

?
?
?
?
Run Code Online (Sandbox Code Playgroud)

然后返回:

?
    60 2E 02 .
?
    60 7A .
?
    60 7A .
?
    FF FD .
Run Code Online (Sandbox Code Playgroud)

然后,检查“?”的字符属性。在http://unicode.org/cldr/utility/character.jsp?a=fffd并看到第 1 级排序键(即FF FD)与“uca”属性匹配。单击“uca”属性会将您带到搜索页面 – http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D – 仅显示 1 个匹配项。并且,在allkeys.txt文件中,级别 1 排序权重显示为0F12,并且只有 1 个匹配项。

为了确保我们正确解释的行为,我看着另一个角色:希腊大写字母OMICRON与VARIA?http://unicode.org/cldr/utility/character.jsp?a=1FF8具有“UCA”(即 1 级排序权重/整理元素)5F30。单击“5F30”会将我们带到搜索页面 – http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D – 显示 30 个匹配项,其中 20 个它们在 0 - 65535 范围内(即 U+0000 - U+FFFF)。查看代码点1FF8allkeys.txt文件,我们看到 1 级排序权重为。在 Notepad++ 中进行“计数”12E012E0. 显示 30 个匹配项(这与 Unicode.org 的结果匹配,但不能保证匹配,因为该文件适用于 Unicode v 5.0 并且该站点使用的是 Unicode v 9.0 数据)。

在 SQL Server 中,以下查询返回 20 个匹配项,与删除 10 个补充字符时的 Unicode.org 搜索相同:

;WITH cte AS
(
  SELECT TOP (65535) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
  FROM   [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARCHAR(50), CONVERT(VARBINARY(2), cte.Num), 2) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0x1FF8) COLLATE Latin1_General_100_CI_AI
ORDER BY cte.Num;
Run Code Online (Sandbox Code Playgroud)

并且,为了确定,返回 ICU Collat​​ion Demo 页面,并将“输入”框中的字符替换为以下 3 个字符,这些字符取自 SQL Server 的 20 个结果列表:

?

?
Run Code Online (Sandbox Code Playgroud)

表明它们确实都具有相同的5F 301 级排序权重(与字符属性页面上的“uca”字段匹配)。

SO,它的确看起来好像这个特定字符应该匹配任何东西。

它是如何工作的(至少在微软领域)

与 SQL Server 不同,.NET 有一种方法可以通过CompareInfo.GetSortKey Method显示字符串的排序键。使用此方法并仅传入 U+FFFD 字符,它会返回排序键0x0101010100. 然后,遍历 0 - 65535 范围内的所有字符以查看哪些字符具有0x0101010100返回 4529 个匹配项的排序键。这与 SQL Server 中返回的 5840 不完全匹配(使用Latin1_General_100_CS_AS_WS排序规则时),但鉴于我正在运行 Windows 10 和 .NET Framework 4.6.1 版(使用 Unicode v),这是我们可以得到的(目前)最接近的6.3.0 根据CharUnicodeInfo 类的图表(在“来电者须知”,“备注”部分)。目前我正在使用 SQLCLR 函数,因此无法更改目标框架版本。当我有机会时,我将创建一个控制台应用程序并使用 4.5 的目标框架版本,因为它使用 Unicode v 5.0,它应该与 100 系列排序规则相匹配。

这个测试表明,即使 .NET 和 SQL Server 之间没有完全相同数量的 U+FFFD 匹配,很明显这不是SQL Server 特定的行为,无论是有意的还是在实施过程中的疏忽微软表示,U+FFFD 字符确实匹配了相当多的字符,即使它不应该根据 Unicode 规范。而且,鉴于此字符与 U+0000 (null) 匹配,这可能只是缺少权重的问题。

关于=查询与LIKE N'%?%'查询中行为的差异,它与通配符和这些(即? ? ? ?)字符的缺失(我假设)权重有关。如果LIKE条件更改为简单,LIKE N'?'则它返回与=条件相同的 3 行。如果通配符的问题不是由于“丢失”权重(顺便说一句,没有0x00返回排序键CompareInfo.GetSortKey),那么这可能是由于这些字符可能具有允许排序键根据上下文(即周围字符)而变化的属性)。