Порівнювання одного "символу" (який може складатися з декількох точок коду: сурогатних пар, об'єднання символів тощо) з іншим походить на досить складному наборі правил. Він настільки складний, тому що потрібно враховувати всі різні (а іноді і "хитрі") правила, які містяться у всіх мовах, представлених у специфікації Unicode . Ця система застосовується до небінарних зіставлень для всіх NVARCHAR
даних, а також для VARCHAR
даних, що використовують зіставлення Windows, а не збір SQL Server (один починається з SQL_
). Ця система не застосовується до VARCHAR
даних за допомогою SQL Server Collation, оскільки вони використовують прості відображення.
Більшість правил визначені в алгоритмі зібрання Unicode (UCA) . Деякі з цих правил, а деякі не поширюються на УКА, такі:
- Замовлення / вага за замовчуванням, наведений у
allkeys.txt
файлі (зазначено нижче)
- Які чутливості та варіанти використовуються (наприклад, це чутливість до регістру чи нечутливість ?, а якщо чутливі, то це перший верхній чи нижній регістр спочатку?)
- Будь-які зміни на основі локальних даних
- Використовується версія стандарту Unicode.
- "Людський" фактор (тобто 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_AI
Collation):
�
Ԩ
ԩ
Ԫ
і це повертає:
Ԫ
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_WS
Collation), але це найближче, що ми можемо отримати (поки що), враховуючи, що я використовую 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.GetSortKey
btw), це може бути пов'язано з тим, що ці символи потенційно можуть мати властивість, яка дозволяє ключу сортування змінюватись залежно від контексту (тобто оточуючих символів ).
FFFD
(пошук*0F12.0020.0002.FFFD
лише повертає один результат). Зі спостереження @ Фореста, що всі вони відповідають порожній рядку і трохи більше читаючи на цю тему, схоже, вага, яку вони ділять у різних бінарних порівняннях, насправді дорівнює нулю, я вважаю.