A comparação de um "caractere" (que pode ser composto de vários pontos de código: pares substitutos, caracteres combinados etc.) com outro baseia-se em um conjunto de regras bastante complexo. É tão complexo devido à necessidade de considerar todas as várias regras (e às vezes "malucas") encontradas em todos os idiomas representados na especificação Unicode . Este sistema se aplica a agrupamentos não binários para todos os NVARCHAR
dados e para os VARCHAR
dados que estão usando um agrupamento do Windows e não um agrupamento do SQL Server (um começando com SQL_
). Este sistema não se aplica aos VARCHAR
dados usando um agrupamento do SQL Server, pois eles usam mapeamentos simples.
A maioria das regras é definida no Algoritmo de Collation Unicode (UCA) . Algumas dessas regras, e outras não cobertas pela UCA, são:
- A ordem / peso padrão fornecidos no
allkeys.txt
arquivo (anotado abaixo)
- Quais sensibilidades e opções estão sendo usadas (por exemplo, é sensível a maiúsculas ou minúsculas? E, se for sensível, é maiúscula primeiro ou minúscula primeiro?)
- Qualquer substituição baseada em código de idioma.
- A versão do padrão Unicode está sendo usada.
- O fator "humano" (ou seja, Unicode é uma especificação, não um software e, portanto, fica a critério de cada fornecedor implementá-lo)
Enfatizei que o ponto final em relação ao fator humano deve deixar claro que não se deve esperar que o SQL Server sempre se comporte 100% de acordo com a especificação.
O fator primordial aqui é a ponderação atribuída a cada ponto de código e o fato de que vários pontos de código podem compartilhar a mesma especificação de peso. Você pode encontrar os pesos básicos (sem substituições específicas do código do idioma) aqui (acredito que a 100
série de Collations é Unicode v 5.0 - confirmação informal nos comentários no item do Microsoft Connect ):
http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt
O ponto de código em questão - U + FFFD - é definido como:
FFFD ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER
Essa notação é definida na seção 9.1 Formato de arquivo Allkeys do UCA:
<entry> := <charList> ';' <collElement>+ <eol>
<charList> := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt> := "*" | "."
Collation elements marked with a "*" are variable.
Essa última linha é importante, pois o Code Point que estamos vendo possui uma especificação que de fato começa com "*". Na seção 3.6 Ponderação variável, existem quatro comportamentos possíveis definidos, com base nos valores de configuração do Collation aos quais não temos acesso direto (eles são codificados na implementação da Microsoft de cada Collation, como se diferencia maiúsculas de minúsculas ou maiúscula primeiro, uma propriedade que é diferente entre os VARCHAR
dados usando SQL_
Collations e todas as outras variações).
Não tenho tempo para fazer uma pesquisa completa sobre os caminhos a serem seguidos e inferir quais opções estão sendo usadas, de modo que uma prova mais sólida possa ser fornecida, mas é seguro dizer que, dentro de cada especificação do Code Point, algo ou não é considerado "igual" nem sempre usará a especificação completa. Nesse caso, temos "0F12.0020.0002.FFFD" e, provavelmente, são apenas os níveis 2 e 3 que estão sendo usados (por exemplo, 0020.0002. ). Fazendo uma "contagem" no Notepad ++ para ".0020.0002". encontra 12.581 correspondências (incluindo caracteres suplementares com os quais ainda não lidamos). Fazer uma "contagem" em "[*" retorna 4049 correspondências. Fazendo um RegEx "Find" / "Count" usando um padrão de\[\*\d{4}\.0020\.0002
retorna 832 correspondências. Portanto, em algum lugar dessa combinação, além de outras regras que não estou vendo, além de alguns detalhes de implementação específicos da Microsoft, está a explicação completa desse comportamento. E, para ficar claro, o comportamento é o mesmo para todos os caracteres correspondentes, pois todos combinam entre si, pois todos têm o mesmo peso após a aplicação das regras (ou seja, essa pergunta poderia ter sido feita sobre qualquer um deles, não necessariamente Sr. �
).
Você pode ver com a consulta abaixo e alterar a COLLATE
cláusula conforme os resultados abaixo da consulta como as várias sensibilidades funcionam nas duas versões dos Collations:
;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;
As várias contagens de caracteres correspondentes em diferentes agrupamentos estão abaixo.
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
Em todos os agrupamentos listados acima, N'' = N'�'
também é avaliado como verdadeiro.
ATUALIZAR
Pude fazer um pouco mais de pesquisa e aqui está o que encontrei:
Como "provavelmente" deve funcionar
Usando a demonstração de agrupamento da ICU , defino o código do idioma como "en-US-u-va-posix", defino a força como "primário", marquei a opção "chaves de classificação" e colei os 4 caracteres a seguir que copiei do resultados da consulta acima (usando o Latin1_General_100_CI_AI
agrupamento):
�
Ԩ
ԩ
Ԫ
e que retorna:
Ԫ
60 2E 02 .
Ԩ
60 7A .
ԩ
60 7A .
�
FF FD .
Em seguida, verifique as propriedades dos caracteres para " " em http://unicode.org/cldr/utility/character.jsp?a=fffd e veja se a chave de classificação de nível 1 (ou seja FF FD
) corresponde à propriedade "uca". Clicar nessa propriedade "uca" leva a uma página de pesquisa - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D - mostrando apenas 1 correspondência. E, no arquivo allkeys.txt , o peso da classificação de nível 1 é mostrado como 0F12
, e há apenas 1 correspondência para isso.
Para ter certeza de que estamos interpretando o comportamento corretamente, olhei para outro personagem: LETRA DE CAPITAL GREGA OMICRON WITH VARIA Ὸ
em http://unicode.org/cldr/utility/character.jsp?a=1FF8 que possui um "uca" ( ou seja, peso de classificação de nível 1 / elemento de classificação) de 5F30
. Clicar nesse "5F30" nos leva a uma página de pesquisa - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D - mostrando 30 correspondências, 20 de estando na faixa de 0 - 65535 (ou seja, U + 0000 - U + FFFF). Olhando no arquivo allkeys.txt para o Code Point 1FF8 , vemos um peso de classificação de nível 1 12E0
. Fazendo uma "contagem" no Notepad ++ em12E0.
mostra 30 correspondências (isso corresponde aos resultados do Unicode.org, embora não seja garantido, pois o arquivo é para Unicode v 5.0 e o site está usando dados Unicode v 9.0).
No SQL Server, a seguinte consulta retorna 20 correspondências, iguais à pesquisa Unicode.org ao remover os 10 caracteres suplementares:
;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;
E, apenas para ter certeza, voltando à página de Demonstração de agrupamento da ICU e substituindo os caracteres na caixa "Entrada" pelos 3 caracteres a seguir, retirados da lista de 20 resultados do SQL Server:
Ὂ
𝜪
Ὸ
mostra que, de fato, todos têm o mesmo 5F 30
peso de classificação de nível 1 (correspondendo ao campo "uca" na página de propriedades do personagem).
Portanto, certamente parece que esse personagem em particular não deve corresponder a mais nada.
Como ele realmente funciona (pelo menos no Microsoft land)
Diferentemente do SQL Server, o .NET tem um meio de mostrar a chave de classificação de uma string por meio do CompareInfo.GetSortKey Method . Usando esse método e passando apenas o caractere U + FFFD, ele retorna uma chave de classificação de 0x0101010100
. Em seguida, iterou sobre todos os caracteres no intervalo de 0 a 65535 para ver quais tinham uma chave de classificação das 0x0101010100
correspondências 4529 retornadas. Isso não corresponde exatamente ao 5840 retornado no SQL Server (ao usar o Latin1_General_100_CS_AS_WS
Collation), mas é o mais próximo que podemos chegar (por enquanto), pois estou executando o Windows 10 e o .NET Framework versão 4.6.1, que usa Unicode v 6.3.0 de acordo com o gráfico da classe CharUnicodeInfo(em "Observação para os chamadores", na seção "Comentários"). No momento, estou usando uma função SQLCLR e, portanto, não posso alterar a versão do Framework de destino. Quando tiver a chance, criarei um aplicativo de console e utilizarei uma versão de destino do Framework 4.5, que usa Unicode v 5.0, que deve corresponder aos agrupamentos da série 100.
O que esse teste mostra é que, mesmo sem o mesmo número exato de correspondências entre o .NET e o SQL Server para U + FFFD, é bastante claro que esse não é um comportamento específico do SQL Server e que, intencionalmente ou supervisionado, com a implementação realizada pela Microsoft, o caractere U + FFFD realmente corresponde a alguns caracteres, mesmo que não corresponda à especificação Unicode. E, como esse caractere corresponde a U + 0000 (nulo), provavelmente é apenas uma questão de pesos ausentes.
ALÉM DISSO
Em relação à diferença de comportamento entre a =
consulta e a LIKE N'%�%'
consulta, ela tem a ver com os curingas e os pesos ausentes (presumo) para esses � Ƕ Ƿ Ǹ
caracteres (ou seja ). Se a LIKE
condição for alterada para simplesmente LIKE N'�'
, ela retornará as mesmas 3 linhas da =
condição. Se o problema com os curingas não for devido a pesos "ausentes" (não há 0x00
uma chave de classificação retornada por CompareInfo.GetSortKey
, btw), pode ser porque esses caracteres possuam uma propriedade que permita que a chave de classificação varie com base no contexto (ou seja, caracteres adjacentes )
FFFD
(procurar*0F12.0020.0002.FFFD
apenas retorna um resultado). Pela observação de @ Forrest de que todos eles correspondem à sequência vazia e um pouco mais de leitura sobre o assunto, parece que o peso que eles compartilham nas várias ordenações não binárias é, na verdade, zero, creio.