Почему поиск LIKE N '% %' соответствует любому символу Юникода, а = N' 'соответствует многим?

21
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 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.

Я предполагаю, что это группа "не взвешенных символов", упомянутых в документации, Collation и Unicode Support .

Мартин Смит
источник

Ответы:

9

Сравнение одного «символа» (который может состоять из нескольких кодовых точек: суррогатных пар, объединения символов и т. Д.) Основано на довольно сложном наборе правил. Это настолько сложно из-за необходимости учитывать все различные (а иногда и «причудливые») правила, присутствующие во всех языках, представленных в спецификации Unicode . Эта система применяется к недвоичным сортировкам для всех NVARCHARданных и для VARCHARданных, использующих сортировку Windows, а не сортировку SQL Server (одна из которых начинается с SQL_). Эта система не применяется к VARCHARданным, использующим параметры сортировки SQL Server, поскольку они используют простые сопоставления.

Большинство правил определены в Unicode Collation 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

Это обозначение определено в разделе 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 «Взвешивание переменных» определены четыре возможных поведения, основанные на значениях конфигурации Collation, к которым у нас нет прямого доступа (они жестко запрограммированы в реализации Microsoft для каждого Collation, например, учитывает ли регистр символов сначала строчные буквы или сначала заглавными буквами, свойство, которое отличается между VARCHARданными, использующими SQL_сопоставления и все другие варианты).

У меня нет времени на полное исследование того, какие пути выбраны, и на то, чтобы определить, какие варианты используются, чтобы дать более убедительные доказательства, но можно с уверенностью сказать, что в каждой спецификации Code Point, независимо от того, что-то или нет считается "равным" не всегда использовать полную спецификацию. В этом случае у нас есть «0F12.0020.0002.FFFD», и, скорее всего, используются только уровни 2 и 3 (т.е. .0020.0002. ). Выполнение «Count» в Notepad ++ для «.0020.0002». находит 12 581 совпадение (включая дополнительные символы, с которыми мы еще не имели дело). Выполнение «Count» для «[*» возвращает 4049 совпадений. Выполнение RegEx "Find" / "Count" с использованием шаблона\[\*\d{4}\.0020\.0002возвращает 832 совпадений. Таким образом, где-то в этой комбинации плюс, возможно, некоторые другие правила, которые я не вижу, плюс некоторые подробности реализации, относящиеся к Microsoft, является полным объяснением этого поведения. И чтобы быть ясным, поведение одинаково для всех совпадающих символов, так как все они соответствуют друг другу, так как все они имеют одинаковый вес после применения правил (то есть, этот вопрос можно было задать по любому из них, не обязательно мистер ).

С помощью приведенного ниже запроса и изменения COLLATEпредложения в соответствии с приведенными ниже результатами запроса можно увидеть, как работают различные функции чувствительности в двух версиях Collation:

;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'�'также оценивается как истина.

ОБНОВИТЬ

Я смог сделать немного больше исследований, и вот что я нашел:

Как это "вероятно" должно работать

Используя демонстрацию сортировки ICU , я установил локаль на «en-US-u-va-posix», установил силу на «первичную», установил флажок «Показать ключи сортировки» и вставил следующие 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 совпадение.

Чтобы убедиться, что мы правильно интерпретируем поведение, я посмотрел на другого персонажа: ПИСЬМО ГРЕЧЕСКОГО КАПИТАЛА с 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 для Code Point 1FF8 , мы видим вес сортировки уровня 1 12E0. Делать "Подсчет" в Notepad ++ на12E0. показывает 30 совпадений (это совпадает с результатами Unicode.org, хотя это не гарантируется, поскольку файл предназначен для Unicode v 5.0, а сайт использует данные Unicode v 9.0).

В SQL Server следующий запрос возвращает 20 совпадений, аналогично поиску Unicode.org при удалении 10 дополнительных символов:

;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 и заменим символы в поле «Ввод» следующими 3 символами, взятыми из списка 20 результатов из SQL Server:


𝜪

показывает, что все они имеют одинаковый 5F 30вес сортировки уровня 1 (соответствует полю "uca" на странице свойств персонажа).

ТАК, конечно, кажется, что этот конкретный персонаж не должен соответствовать чему-либо еще.

Как это на самом деле работает (по крайней мере, в Microsoft-Land)

В отличие от SQL Server, в .NET есть средство отображения ключа сортировки для строки с помощью метода CompareInfo.GetSortKey . Используя этот метод и передавая только символ U + FFFD, он возвращает ключ сортировки 0x0101010100. Затем, перебирая все символы в диапазоне от 0 до 65535, чтобы увидеть, какие из них имеют ключ сортировки, 0x0101010100вернувший 4529 совпадений. Это не совсем совпадает с 5840, возвращаемым в SQL Server (при использовании Latin1_General_100_CS_AS_WSCollation), но это самое близкое, что мы можем получить (на данный момент), учитывая, что я использую Windows 10 и .NET Framework версии 4.6.1, которая использует Unicode v 6.3.0 в соответствии с диаграммой для класса CharUnicodeInfo(в разделе «Примечание для абонентов», в разделе «Замечания»). На данный момент я использую функцию SQLCLR и поэтому не могу изменить целевую версию Framework. Когда у меня будет возможность, я создам консольное приложение и использую целевую версию Framework 4.5, которая использует Unicode v 5.0, которая должна соответствовать параметрам сортировки серии 100.

Этот тест показывает, что даже без точно такого же количества совпадений между .NET и SQL Server для U + FFFD совершенно ясно, что это не специфичное для SQL Server поведение и что это сделано преднамеренно или упущено при реализации Microsoft, символ U + FFFD действительно соответствует довольно многим символам, даже если он не соответствует спецификации Unicode. И, учитывая, что этот символ соответствует U + 0000 (ноль), это, вероятно, просто проблема недостающих весов.

ТАКЖЕ

Что касается различий в поведении в =запросе по сравнению с LIKE N'%�%'запросом, это связано с подстановочными знаками и отсутствующими (я предполагаю) весами для этих (то есть � Ƕ Ƿ Ǹ) символов. Если LIKEусловие изменено на простое, LIKE N'�'то оно возвращает те же 3 строки, что и =условие. Если проблема с подстановочными знаками не связана с «отсутствующими» весами ( кстати, 0x00ключ сортировки не возвращается CompareInfo.GetSortKey, кстати), то это может быть связано с тем, что эти символы потенциально имеют свойство, позволяющее изменять ключ сортировки в зависимости от контекста (т. Е. Окружающие символы ).

Соломон Руцкий
источник
Спасибо - в связанном allkeys.txt это выглядит так, как будто ничто иное не имеет такой же вес, как FFFD(поиск *0F12.0020.0002.FFFDтолько возвращает один результат). Из наблюдения @ Forrest, что все они соответствуют пустой строке и немного больше чтения по теме, похоже, что вес, который они разделяют в различных недвоичных сопоставлениях, на самом деле я считаю нулевым.
Мартин Смит
1
@MartinSmith Провел некоторые исследования с использованием ICU Collation Demo , а также вставил � A a \u24D0и несколько других, которые были в наборе результатов 5839 совпадений. Похоже, вы не можете пропустить первый вес, и этот заменяющий символ - единственный, начинающийся с 0F12. Многие другие также имели уникальный первый вес, и многие полностью отсутствовали в файле allkeys. Так что это может быть ошибкой реализации из-за человеческой ошибки. Я видел этот символ в группе «неподдерживаемые» на сайте Unicode в их чартах Collations. Завтра посмотрю больше.
Соломон Руцкий,
Rextester использует 4.5. Я на самом деле вижу меньше совпадений в этой версии (3385). Может быть, я устанавливаю какой-то другой вариант для вас? rextester.com/JBWIN31407
Мартин Смит
Кстати, этот ключ сортировки 01 01 01 01 00упоминается здесь archives.miloush.net/michkap/archive/2007/09/10/4847780.html (выглядит как CompareInfo.InternalGetSortKeyзвонки LCMapStringEx)
Martin Smith,
@MartinSmith Я немного поиграл с этим, но пока не уверен, какая разница. ОС, на которой работает .NET, имеет значение. Я посмотрю больше завтра, если у меня будет время. Независимо от количества совпадений, тем не менее, это, по крайней мере, подтверждает причину такого поведения, особенно теперь, когда мы имеем некоторое представление о структуре ключа сортировки благодаря блогу, на который вы ссылаетесь, и некоторым другим, связанным с ним. На странице CharUnicodeInfo, на которую я ссылался, упоминаются базовые вызовы Collation, которая является основой для моего предложения здесь: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Соломон Руцки,