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


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

INSERT INTO @T
VALUES      (N'A'),
            (N'B'),
            (N'C'),
            (N'Ƕ'),
            (N'Ƿ'),
            (N'Ǹ');

SELECT *
FROM   @T
WHERE  Col LIKE N'%�%'

退货

Col
A
B
C
Ƕ
Ƿ
Ǹ

SELECT *
FROM   @T
WHERE  Col = N'�' 

退货

Col
Ƕ
Ƿ
Ǹ

使用下面的代码生成每个可能的双字节“字符”表明,该=版本与它们中的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'�'  

任何人都可以对这里发生的事情有所了解吗?

使用COLLATE Latin1_General_BIN然后匹配单个字符NCHAR(65533)-但问题是要了解在其他情况下使用的规则。与匹配的21,229个字符有什么特别之处=,为什么所有内容都与通配符类似?我认为背后有一些我想念的原因。

nchar(65534)[以及其他21k]的工作效果与一样好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 Server2016。该符号是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中提到的“非加权字符”组。

Answers:


8

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

大多数规则在Unicode排序算法(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

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

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

Collation elements marked with a "*" are variable.

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

我没有时间对采用哪种路径进行全面研究,也没有时间推断使用了哪些选项,以便可以给出更可靠的证据,但是可以肯定地说,在每个代码点规范中,无论是否被认为“相等”并不总是使用完整的规范。在这种情况下,我们具有“ 0F12.0020.0002.FFFD”,并且很可能仅使用了第2级和第3级(即.0020.0002。)。在记事本++中为“ .0020.0002”执行“计数”。找到12,581个匹配项(包括我们尚未处理的辅助字符)。对“ [*”进行“计数”将返回4049个匹配项。使用以下模式进行RegEx“查找” /“计数”\[\*\d{4}\.0020\.0002返回832个匹配项。因此,在此组合中的某处,可能还有一些我未看到的其他规则,再加上一些特定于Microsoft的实现详细信息,充分说明了此行为。而且要明确的是,所有匹配字符的行为都是相同的,因为它们相互匹配,因为一旦应用规则,它们的权重都相同(意味着,可能有人问过这个问题,不是一定是先生)。

您可以在下面的查询中看到,并根据查询下面COLLATE的结果更改子句,两种敏感性在Collat​​ions的两个版本中如何起作用:

;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;

下面是在不同归类中匹配字符的各种计数。

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

在上面列出的所有归类中,N'' = N'�'也将评估为true。

更新

我能够进行更多研究,这是我发现的内容:

它“可能”如何工作

使用ICU Collat​​ion Demo,我将语言环境设置为“ en-US-u-va-posix”,将强度设置为“ primary”,选中显示“ sort keys”,并粘贴了以下4个字符,这些字符是我从上面查询的结果(使用Latin1_General_100_CI_AI归类):

�
Ԩ
ԩ
Ԫ

然后返回:

Ԫ
    60 2E 02 .
Ԩ
    60 7A .
ԩ
    60 7A .
�
    FF FD .

然后,在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)。在allkeys.txt文件中查找代码点1FF8,我们看到1级排序权重为12E0。在记事本上执行“计数”12E0. 显示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;

而且,可以肯定的是,返回到ICU排序规则演示页,并使用SQL Server的20个结果列表中的以下3个字符替换“输入”框中的字符:


𝜪

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

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

实际工作方式(至少在Microsoft-land中)

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

该测试表明,即使在.NET和U + FFFD的SQL Server之间没有完全相同的匹配次数,也很清楚这不是 SQL Server特定的行为,是否对实现进行了有意或无意的检查由Microsoft提供的U + FFFD字符确实确实匹配了很多字符,即使它不符合Unicode规范。并且,鉴于此字符匹配U + 0000(空),这可能只是缺少权重的问题。

关于=查询与LIKE N'%�%'查询在行为上的差异,这与通配符和这些(即� Ƕ Ƿ Ǹ)字符缺少的权重(我假设)有关。如果将LIKE条件更改为简单条件,LIKE N'�'则它将返回与=条件相同的3行。如果通配符的问题不是由于权重“丢失”(没有0x00CompareInfo.GetSortKeybtw 返回的排序键),则可能是由于这些字符可能具有允许排序键根据上下文而变化的属性(即,周围的字符) )。


谢谢-在allkeys.txt链接中,似乎没有别的权重相同FFFD(搜索*0F12.0020.0002.FFFD仅返回一个结果)。从@Forrest的观察来看,它们都与空字符串匹配,并且在主题上的阅读更多,因此我认为它们在各种非二进制排序规则中所占的权重实际上为零。
马丁·史密斯

1
@MartinSmith使用ICU Collat​​ion Demo进行了一些研究,并� A a \u24D0输入了5839个比赛结果集中的其他一些结果。看起来您不能跳过第一个权重,而此替换字符是唯一以开头的字符0F12。许多其他文件也具有独特的第一权重,并且allkeys文件完全丢失了许多文件。因此,这可能是由于人为错误导致的实现错误。我确实在其Collat​​ions图表的Unicode站点上的“不受支持”组中看到此字符。明天会更多。
所罗门·鲁茨基

Rextester使用4.5。实际上,该版本(3385)的匹配较少。也许我为您设置了一些其他选项?rextester.com/JBWIN31407
马丁·史密斯

顺便说一句,01 01 01 01 00这里提到了那种排序键,archives.miloush.net / michkap / archive / CompareInfo.InternalGetSortKeyLCMapStringEx
马丁·史密斯

@MartinSmith我玩了一点,但不确定有什么区别。运行.NET的操作系统确实可以发挥作用。如果有时间,我明天还会再看。但是,不管匹配的数目如何,这似乎至少可以确认行为的原因,尤其是由于您链接到的博客以及其中的其他链接,使得我们对排序键结构有了一定的了解。我链接到的CharUnicodeInfo页面提到了基本的归类调用,这是我在这里提出建议的基础:connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.