特殊字符(夏威夷人'Okina)导致奇怪的弦乐行为

时间:2019-04-01 12:29:51

标签: sql-server tsql unicode collation

Hawaiian quote与字符串函数结合使用时,在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 个答案:

答案 0 :(得分:8)

  

在将T-SQL与字符串函数结合使用时,夏威夷报价在T-SQL中具有某些奇怪的行为。 ...其他角色也会遇到同样的问题吗?

几件事:

  1. 这不是夏威夷语的“ quote”:它是影响发音的“ glottal stop”。
  2. 这不是“怪异”的行为:这不是您所期望的。
  3. 此行为不是专门的“问题”,尽管是的,还有其他一些字符也表现出类似的行为。例如,以下字符(上面的U + 02DA环)的行为会略有不同,具体取决于该字符位于哪一侧:

    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归类(名称以{{1} )。

这里的问题不是它不等于任何其他字符(在二进制排序规则之外),实际上它确实等于另一个字符(U+0312 Combining Turned Comma Above):

SQL_

问题在于,这是一个“间距修饰符”字符,因此,根据要处理的修饰符,它会附加到该字符之前或之后并修改其含义/发音。

根据Unicode Standard, Chapter 7 (Europe-I)第7.8节(修饰符),第323页(文档,而非PDF):

  

7.8修饰语字母

     

在Unicode标准中使用的修饰语字母是通常与其他字母相邻并以某种方式修改其用法的字母或符号。它们不是形式上的标记组合(gc = Mn或gc = Mc),并且不与它们修改的基字母图形组合。它们本身就是基本字符。他们修改其他字母的意义更多地取决于其用法的语义;它们通常倾向于像变音符号一样起作用,表明字母的发音发生了变化,或者以其他方式区分了字母的用法。通常,此变音符号修饰符适用于修饰符字母之前的字符,但是修饰符字母有时可以修饰后一个字符。有时修饰语字母可能会单独代表其自身的声音。
  ...

     

间距修饰符字母:U + 02B0–U + 02FF

     

语音用法。。此区块中的大多数修饰语字母都是语音修饰语,包括覆盖国际语音字母所需的字符。在许多情况下,修饰语字母用来表示相邻字母的发音在某种程度上有所不同-因此称为“修饰语”。它们还用于标记重音或音调,或者可以简单地代表自己的声音。


下面的示例应有助于说明。我使用的是100级排序规则,它需要区分重音符号(即名称包含;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 ̒ */ ):

_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(假设您使用的是SQL Server 2005或更高版本)。含义:

  • SQL Server 2000:BIN
  • SQL Server 2005:Latin1_General_BIN
  • SQL Server 2008、2008 R2、2012、2014和2016:Latin1_General_BIN2
  • SQL Server 2017和更高版本:Latin1_General_100_BIN2

如果您好奇我为什么提出该建议,请参阅:

Differences Between the Various Binary Collations (Cultures, Versions, and BIN vs BIN2)

此外,有关归类/ Unicode /编码/等等的更多信息,请访问:Collations Info

答案 1 :(得分:2)

我无法提供详细的答案,但是我可以提供解决方案以满足您的期望。

这与排序规则有关,尽管我不确定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(animation的代码改编))来检查哪个排序规则可以确认您的期望。它将为所有归类评估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);