Чому пошук LIKE N '% %' відповідає будь-якому символу Unicode та = 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 219 з них та у 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 Collation, оскільки вони використовують прості відображення.

Більшість правил визначені в алгоритмі зібрання Unicode (UCA) . Деякі з цих правил, а деякі не поширюються на УКА, такі:

  1. Замовлення / вага за замовчуванням, наведений у allkeys.txtфайлі (зазначено нижче)
  2. Які чутливості та варіанти використовуються (наприклад, це чутливість до регістру чи нечутливість ?, а якщо чутливі, то це перший верхній чи нижній регістр спочатку?)
  3. Будь-які зміни на основі локальних даних
  4. Використовується версія стандарту Unicode.
  5. "Людський" фактор (тобто Unicode - це специфікація, а не програмне забезпечення, і таким чином залишається кожен постачальник для його реалізації)

Я підкреслив, що остаточний момент стосовно людського фактору, сподіваючись, дасть зрозуміти, що не слід очікувати, що SQL Server завжди буде вести себе на 100% відповідно до специфікації.

Переважним фактором тут є зважування, що надається кожній точці коду, і той факт, що кілька точок коду можуть мати однакові специфікації ваги. Ви можете знайти основні ваги (відсутні особливі локальні зміни) тут (я вважаю, що 100серія Collations - це 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 UCA:

<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_Collations та всіма іншими варіантами).

Я не встигаю провести повне дослідження того, які шляхи взяті, і зробити висновок про те, які варіанти використовуються таким чином, щоб можна було отримати більш надійний доказ, але з упевненістю можна сказати, що в межах кожної специфікації Code Point, чи є щось, чи ні вважається "рівним" не завжди використовуватиме повну специфікацію. У цьому випадку у нас є "0F12.0020.0002.FFFD", і, швидше за все, використовуються лише рівні 2 і 3 (тобто .0020.0002. ). Виконайте "Підрахунок" у Блокноті ++ для ".0020.0002." знаходить 12 581 збігів (включаючи додаткових символів, з якими ми ще не мали справу). Здійснення "рахунку" на "[*" повертає 4049 матчів. Робити RegEx "Find" / "Count" за допомогою шаблону\[\*\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;

Нижче наведено різні підрахунки відповідних символів у різних зіставленнях.

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 Collation Demo , я встановив локаль на "en-US-u-va-posix", встановив силу на "первинний", перевірив показ "сортування ключів" і вставив у наступні 4 символи, які я скопіював із результати запиту вище (за допомогою Latin1_General_100_CI_AICollation):

�
Ԩ
ԩ
Ԫ

і це повертає:

Ԫ
    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 збіг.

Щоб переконатися, що ми правильно трактуємо поведінку, я подивився на іншого персонажа: GREEK CAPITAL LETTER OMICRON AND 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. Робити "Підрахунок" у "Блокноті ++"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, а що навмисне чи нагляд за виконанням зроблено Майкрософт, символ U + FFFD дійсно відповідає досить багато символів, навіть якщо він не повинен відповідати специфікації Unicode. І, враховуючи, що цей символ відповідає U + 0000 (нульовий), це, мабуть, лише питання про відсутність ваг.

ТАКОЖ

Що стосується різниці в поведінці в =запиті проти LIKE N'%�%'запиту, то це має відношення до підстановок та відсутніх (я припускаю) ваг для цих (тобто � Ƕ Ƿ Ǹ) символів. Якщо LIKEумова зміниться на просто, LIKE N'�'то вона повертає ті самі 3 рядки, що і =умова. Якщо проблема із символами підстановки не пов’язана з "відсутніми" вагами (немає 0x00ключа сортування, повернутого CompareInfo.GetSortKeybtw), це може бути пов'язано з тим, що ці символи потенційно можуть мати властивість, яка дозволяє ключу сортування змінюватись залежно від контексту (тобто оточуючих символів ).


Дякую - у посиланнях allkeys.txt це виглядає так, ніби більше нічого не надається такої ж ваги, як FFFD(пошук *0F12.0020.0002.FFFDлише повертає один результат). Зі спостереження @ Фореста, що всі вони відповідають порожній рядку і трохи більше читаючи на цю тему, схоже, вага, яку вони ділять у різних бінарних порівняннях, насправді дорівнює нулю, я вважаю.
Мартін Сміт

1
@MartinSmith Провели кілька досліджень, використовуючи демо- версію для порівняння ICU , а також додавши � 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 :-)
Соломон Руцький
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.