Специальный характер (гавайская Окина) приводит к странному поведению строки - PullRequest
14 голосов
/ 01 апреля 2019

Гавайская цитата имеет странное поведение в T-SQL при использовании его в сочетании со строковыми функциями.Что тут происходит?Я что-то пропустил?Другие персонажи страдают от этой же проблемы?

SELECT UNICODE(N'ʻ') -- Returns 699 as expected.

SELECT REPLACE(N'"ʻ', '"', '_') -- Returns "ʻ, I expected _ʻ

SELECT REPLACE(N'aʻ', 'a', '_') -- Returns aʻ, I expected _ʻ

SELECT REPLACE(N'"ʻ', N'ʻ', '_') -- Returns __, I expected "_

SELECT REPLACE(N'-', N'ʻ', '_') -- Returns -, I expected -

Также странно при использовании в LIKE, например:

DECLARE @table TABLE ([Name] NVARCHAR(MAX))
INSERT INTO
    @table
VALUES
    ('John'),
    ('Jane')

SELECT
    *
FROM
    @table
WHERE
    [Name] LIKE N'%ʻ%' -- This returns both records. I expected none.

Ответы [ 2 ]

8 голосов
/ 04 апреля 2019

Гавайская кавычка имеет странное поведение в T-SQL при использовании ее в сочетании со строковыми функциями. ... Другие персонажи страдают от этой же проблемы?

Несколько вещей:

  1. Это не гавайская "цитата": это " гортанная остановка ", которая влияет на произношение.
  2. Это не «странное» поведение: это просто не то, что вы ожидали.
  3. Это поведение не является конкретно "проблемой", хотя да, есть другие персонажи, которые демонстрируют подобное поведение. Например, следующий символ (U + 02DA Ring Above) ведет себя немного по-разному в зависимости от того, на какой стороне символа он находится:

    SELECT REPLACE(N'a˚aa' COLLATE Latin1_General_100_CI_AS, N'˚a',  N'_'); -- Returns a_a
    SELECT REPLACE(N'a˚aa' COLLATE Latin1_General_100_CI_AS, N'a˚',  N'_'); -- Returns _aa
    

Теперь любой, кто использует SQL Server 2008 или новее, должен использовать сопоставление уровня 100 (или новее). Они добавили много весов сортировки и отображений в верхнем и нижнем регистре в серии 100, которые не входят в серию 90, или в ненумерованные серии, или в основном устаревшие параметры сортировки SQL Server (имена которых начинаются с SQL_).

Проблема здесь не в том, что он не приравнивается к какому-либо другому символу (кроме двоичного сопоставления), а на самом деле он приравнивается к одному другому символу ( U + 0312 Объединение перевернутой запятой выше ):

;WITH nums AS
(
  SELECT TOP (65536) (ROW_NUMBER() OVER (ORDER BY @@MICROSOFTVERSION) - 1) AS [num]
  FROM   [master].sys.all_columns ac1
  CROSS JOIN   [master].sys.all_columns ac2
)
SELECT nums.[num] AS [INTvalue],
       CONVERT(BINARY(2), nums.[num]) AS [BINvalue],
       NCHAR(nums.[num]) AS [Character]
FROM   nums
WHERE  NCHAR(nums.[num]) = NCHAR(0x02BB) COLLATE Latin1_General_100_CI_AS;
/*
INTvalue    BINvalue    Character
699         0x02BB      ʻ
786         0x0312      ̒
*/

Проблема в том, что это символ «модификатор интервала», и поэтому он присоединяет и изменяет значение / произношение символа до или после него, в зависимости от того, с каким символом модификатора вы имеете дело.

Согласно стандарту Юникод, глава 7 (Европа-I) , раздел 7.8 (буквы-модификаторы), стр. 323 (документа, а не документа PDF):

7.8 Буквы модификатора

Буквы-модификаторы в том смысле, в каком они используются в Стандарте Юникод, - это буквы или символы, которые обычно пишутся рядом с другими буквами и которые каким-то образом модифицируют их использование. Они формально не объединяют метки (gc = Mn или gc = Mc) и не графически объединяются с базовой буквой, которую они изменяют. Они являются базовыми персонажами сами по себе. Смысл, в котором они изменяют другие буквы, больше зависит от их семантики в использовании; они часто имеют тенденцию функционировать, как если бы они были диакритиками, указывая на изменение в произношении буквы или иным образом отличая использование буквы. Обычно эта диакритическая модификация применяется к символу, предшествующему букве модификатора, но буквы модификатора могут иногда модифицировать следующий символ. Иногда буква-модификатор может просто стоять отдельно, представляя собственный звук.
...

Буквы модификатора интервала: U + 02B0 – U + 02FF

Фонетическое использование. Большинство букв-модификаторов в этом блоке являются фонетическими модификаторами, включая символы, необходимые для покрытия международного фонетического алфавита. Во многих случаях буквы-модификаторы используются для указания того, что произношение соседней буквы каким-то образом отличается - отсюда и название «модификатор». Они также используются для обозначения ударения или тона или могут просто представлять их собственный звук.


Приведенные ниже примеры должны помочь проиллюстрировать. Я использую сопоставление уровня 100, и оно должно быть чувствительным к акценту (то есть имя содержит _AS):

SELECT REPLACE(N'ʻ'    COLLATE Latin1_General_100_CI_AS, N'ʻ',   N'_'); -- Returns _
SELECT REPLACE(N'ʻa'   COLLATE Latin1_General_100_CI_AS, N'ʻ',   N'_'); -- Returns _a
SELECT REPLACE(N'ʻaa'  COLLATE Latin1_General_100_CI_AS, N'ʻ',   N'_'); -- Returns _aa
SELECT REPLACE(N'aʻaa' COLLATE Latin1_General_100_CI_AS, N'ʻ',   N'_'); -- Returns __aa

SELECT REPLACE(N'ʻaa'  COLLATE Latin1_General_100_CI_AS, N'ʻa',  N'_'); -- Returns ʻ__
SELECT REPLACE(N'aʻaa' COLLATE Latin1_General_100_CI_AS, N'ʻa',  N'_'); -- Returns aʻ__

SELECT REPLACE(N'aʻaa' COLLATE Latin1_General_100_CI_AS, N'aʻ',  N'_'); -- Returns _aa
SELECT REPLACE(N'aʻaa' COLLATE Latin1_General_100_CI_AS, N'aʻa', N'_'); -- Returns _a

SELECT REPLACE(N'aʻaa' COLLATE Latin1_General_100_CI_AS, N'a',   N'_'); -- Returns aʻ__
SELECT REPLACE(N'אʻaa' COLLATE Latin1_General_100_CI_AS, N'א',   N'_'); -- Returns אʻaa
SELECT REPLACE(N'ffʻaa' COLLATE Latin1_General_100_CI_AS, N'ff',   N'_'); -- Returns ffʻaa
SELECT REPLACE(N'ffaa'  COLLATE Latin1_General_100_CI_AS, N'ff',   N'_'); -- Returns _aa



SELECT CHARINDEX(N'a', N'aʻa' COLLATE Latin1_General_100_CI_AS); -- 3
SELECT CHARINDEX(N'a', N'aʻa' COLLATE Latin1_General_100_CI_AI); -- 1



SELECT 1 WHERE N'a' = N'aʻ' COLLATE Latin1_General_100_CI_AS; -- (0 rows returned)
SELECT 2 WHERE N'a' = N'aʻ' COLLATE Latin1_General_100_CI_AI; -- 2

Если вам нужно иметь дело с такими символами так, чтобы игнорировать их предполагаемое языковое поведение, тогда да, вы должны использовать двоичное сопоставление. В таких случаях, пожалуйста, используйте самый последний уровень сортировки и BIN2 вместо BIN (при условии, что вы используете SQL Server 2005 или новее). Значение:

  • SQL Server 2000: Latin1_General_BIN
  • SQL Server 2005: Latin1_General_BIN2
  • SQL Server 2008, 2008 R2, 2012, 2014 и 2016: Latin1_General_100_BIN2
  • SQL Server 2017 и новее: Japanese_XJIS_140_BIN2

Если вам интересно, почему я даю эту рекомендацию, см .:

Различия между различными двоичными сопоставлениями (культуры, версии и BIN против BIN2)

И,для получения дополнительной информации о сопоставлениях / Unicode / кодировках / и т. д., пожалуйста, посетите: Информация сопоставления

2 голосов
/ 01 апреля 2019

Я не могу дать подробный ответ, но я могу предоставить решение, соответствующее вашим ожиданиям.

Это связано с сопоставлениями, хотя я не уверен, почему сопоставления Windows дают неожиданные результаты. Если вы используете двоичное сопоставление, вы получите ожидаемые результаты (см. Отличный ответ Соломона, для которого нужно использовать BIN):

SELECT REPLACE(N'aʻ' COLLATE Latin1_General_BIN, N'a', N'_') 

Возвращает

DECLARE @table TABLE ([Name] NVARCHAR(MAX))
INSERT INTO
    @table
VALUES
    (N'John'),
    (N'Jane'),
    (N'Hawaiʻi'),
    (N'Hawai''i'),
    (NCHAR(699))

SELECT
    *
FROM
    @table
WHERE
    [Name] like N'%ʻ%' COLLATE Latin1_General_BIN

Возвращает:

Hawaiʻi
ʻ

Вы можете проверить, какое сопоставление подтверждает ваши ожидания, с помощью следующего кода (адаптировано из кода @SolomonRutzky ( source )). Он оценивает SELECT REPLACE(N'"ʻ', N'ʻ', N'_')) = '"_' для всех сопоставлений:

DECLARE @SQL NVARCHAR(MAX) = N'DECLARE @Counter INT = 1;';

SELECT @SQL += REPLACE(N'
  IF((SELECT REPLACE(N''"ʻ'' COLLATE {Name}, N''ʻ'', N''_'')) = ''"_'')
  BEGIN
    RAISERROR(N''%4d.  {Name}'', 10, 1, @Counter) WITH NOWAIT;
    SET @Counter += 1;
  END;
', N'{Name}', col.[name]) + NCHAR(13) + NCHAR(10)
FROM   sys.fn_helpcollations() col
ORDER BY col.[name]

--PRINT @SQL;
EXEC (@SQL);
...