Por que essas duas SELECT
instruções resultam em uma ordem de classificação diferente?
USE tempdb;
CREATE TABLE dbo.OddSort
(
id INT IDENTITY(1,1) PRIMARY KEY
, col1 NVARCHAR(2)
, col2 NVARCHAR(2)
);
GO
INSERT dbo.OddSort (col1, col2)
VALUES (N'e', N'eA')
, (N'é', N'éB')
, (N'ë', N'ëC')
, (N'è', N'èD')
, (N'ê', N'êE')
, (N'ē', N'ēF');
GO
SELECT *
FROM dbo.OddSort
ORDER BY col1 COLLATE Latin1_General_100_CS_AS;
╔════╦══════╦══════╗ ║ id ║ col1 ║ col2 ║ ╠════╬══════╬══════╣ ║ 1 ║ e ║ eA ║ ║ 2 ║ é ║ éB ║ ║ 4 ║ è ║ èD ║ -- deve ser id 3? ║ 5 ║ ê ║ êE ║ ║ 3 ║ ë ║ ë C ║ ║ 6 ║ ē ║ ēF ║ ╚════╩══════╩══════╝
SELECT *
FROM dbo.OddSort
ORDER BY col2 COLLATE Latin1_General_100_CS_AS;
╔════╦══════╦══════╗ ║ id ║ col1 ║ col2 ║ ╠════╬══════╬══════╣ ║ 1 ║ e ║ eA ║ ║ 2 ║ é ║ éB ║ ║ 3 ║ ë ║ ë C ║ ║ 4 ║ è ║ èD ║ ║ 5 ║ ê ║ êE ║ ║ 6 ║ ē ║ ēF ║ ╚════╩══════╩══════╝
O comportamento que você está vendo aqui se deve, em um sentido geral, ao fato de que o Unicode Collation Algorithm (UCA) permite uma classificação complexa em vários níveis. Mais especificamente:
Ordenação não é comparação:
Determinar se duas strings são iguais ou diferentes é bastante simples (dado um local/idioma específico e um conjunto de sensibilidades). Mas determinar a ordem de 2 ou mais strings pode ser altamente complexo.
A classificação é feita em uma série de etapas, com cada etapa aplicada à string inteira, não caractere por caractere:
Quando você classifica por
col1
(caractere único), primeiro determina que todos os caracteres têm o mesmo peso, pois são todos " e ". Em seguida, aplica os pesos acentos/diacríticos. Não há diferenças de caixa, então a terceira etapa não mudaria nada. Portanto, as únicas diferenças estão na etapa 2, e é por isso que há uma ordem preferencial para essas linhas com base emcol1
.Quando você classifica por
col2
(dois caracteres), primeiro determina que cada linha tem um peso diferente, pois ambos os caracteres são usados para determinar o peso de classificação (por exemplo, " ea ", " eb ", etc). Em seguida, aplica os pesos acentos/diacríticos. Não há diferenças de caixa, então a terceira etapa não mudaria nada. Portanto, há diferenças nas etapas 1 e 2 desta vez. Mas como as diferenças na etapa 1 já foram aplicadas a cada string antes que os pesos da etapa 2 sejam considerados, os pesos da etapa 2 não têm nenhum efeito na ordenação; eles só se aplicariam se os pesos da etapa 1 para duas ou mais linhas fossem os mesmos.A adaptação a seguir do código de exemplo da pergunta ilustra o comportamento de classificação descrito acima. Adicionei algumas linhas adicionais e uma coluna adicional para ajudar a mostrar o impacto do Collation diferenciando maiúsculas de minúsculas (já que os dados de amostra originais são todos em minúsculas):
CONFIGURAR
TESTE 1
Devoluções:
O que podemos ver nos resultados acima:
TESTE 2
Devoluções:
O que podemos ver nos resultados acima:
TESTE 3
Devoluções:
O que podemos ver nos resultados acima:
col2
na pergunta é novamente devido à "Comparação de vários níveis" e não à "Sensibilidade contextual".Notas Adicionais:
Com relação a obter as regras exatas, isso não é tão fácil quanto deveria ser. O problema em obter explicações concretas dessas regras é que as regras de classificação Unicode, embora definitivamente documentadas, são uma recomendação. Cabe aos fornecedores, como a Microsoft, implementar essas recomendações. A Microsoft não implementou as recomendações exatamente como indicado na documentação do Unicode, portanto, há uma desconexão (semelhante a como as especificações HTML ou CSS não são implementadas completamente, nem mesmo da mesma maneira, entre os fornecedores). Então, existem diferentes versões do Windows Collations (você está usando a versão
100
que saiu com o SQL Server 2008) e que está vinculada a uma versão Unicode que é muito mais antiga que a versão atual do Unicode ou do ICU Collation demoestá usando. Por exemplo, a seção O que há de novo no SQL Server 2008 Collations da documentação "Collation and Unicode Support" para SQL Server 2008 apresenta 2 pontos muito interessantes sobre o que há de "novo" na_100_
série Collations:O Unicode 5.0 foi publicado em julho de 2006 (bem, o banco de dados de caracteres foi lançado então, e a especificação completa seguiu no final de 2006). A versão atual é 10.0 que foi publicada em junho de 2017. E dado o padrão de lançamento dos últimos 4 anos, é provável que a versão 11.0 seja lançada em meados de 2018.
Esses pesos foram mais do que provavelmente definidos no Padrão Unicode, mas não nesta implementação.
Ainda assim, a documentação do UCA vinculada acima é um bom lugar para começar.
As chaves de classificação usadas pelo Windows / .NET / SQL Server não são exatamente as mesmas mostradas no padrão Unicode (consulte a resposta de @Patrick) ou implementadas no ICU . Para ver o que o Windows / .NET / SQL Server usa, você pode tentar o método CompareInfo.GetSortKey . Criei uma UDF SQLCLR para passar esses valores e obter a chave de classificação. Observe que estou usando o SQL Server 2017 no Windows 10 com o .NET Framework 4.5 - 4.6.1 instalado, portanto, o .NET deve usar o Unicode 6.0.0. Além disso, Level4 não está sendo usado para essas strings.
Observar essas chaves de classificação para o Teste 1 e perceber que os níveis são classificados como várias colunas dentro de uma
ORDER BY
cláusula (L3 é classificado dentro dos mesmos valores de L2, que é classificado dentro dos mesmos valores de L1), deve ilustrar que o motivo do comportamento observado na pergunta é, de fato, a capacidade de classificação em vários níveis do Unicode. Da mesma maneira:Observando algumas das chaves de classificação para o Teste 2, podemos ver que os caracteres básicos são classificados primeiro (L1), depois os acentos são classificados (L2) e, em seguida, as maiúsculas são classificadas (L3).
Como o tipo de dados é
NVARCHAR
, estamos preocupados apenas com pontos de código Unicode e algoritmos de classificação, daí o uso daUNICODE()
função em TEST 1. Embora as páginas de código sejam especificadas pela maioria dos agrupamentos, elas pertencem apenas aosVARCHAR
dados. Ou seja, enquanto a página de código 1252 é especificada pelaLatin1_General*
série de agrupamentos, isso pode ser ignorado aqui.Os pesos descritos na Default Unicode Collation Element Table (DUCET) ( Versão 5.0.0 , que deve mapear para a
_100_
série Collations) são adequados para o inglês dos EUA, mas não para outras localidades/idiomas. Outros idiomas precisam começar com o DUCET e, em seguida, aplicar regras de substituição específicas de localidade, conforme definido pelo projeto Common Locale Data Repository (CLDR). Pelo que posso dizer, as versões 1.4 / 1.4.1 foram lançadas em 2006. Para obter essas substituições, baixe o arquivo "core" do CLDR 1.4 em http://unicode.org/Public/cldr/1.4.0/core.zip , então, nesse arquivo zip, vá para a pasta de agrupamento e encontre o arquivo XML correspondente à localidade que está sendo usada. Esses arquivos contêm apenas as substituições e não são conjuntos completos de regras de agrupamento.Esta questão não está tão relacionada a bancos de dados, mas mais sobre manipulação e regras de Unicode.
Com base em https://learn.microsoft.com/en-us/sql/t-sql/statements/windows-collation-name-transact-sql Latin1_General_100_CS_AS significa: "O agrupamento usa as regras de classificação do dicionário geral Latin1 e mapeia para a página de código 1252" com o acréscimo de CS = Case Sensitive e AS = Accent Sensitive.
O mapeamento entre a página de código do Windows 1252 e Unicode ( http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT ) mostra os mesmos valores para todos os caracteres com os quais estamos lidando (exceto e com macron que não existe no mapeamento da Microsoft, então não faço ideia do que ele faz com este caso), então podemos nos concentrar nas ferramentas e terminologia Unicode por enquanto.
Primeiro, deixe-nos saber exatamente com o que estamos lidando, para todas as suas strings:
O algoritmo de agrupamento Unicode é descrito aqui: https://www.unicode.org/reports/tr10/
Dê uma olhada na seção 1.3 "Sensibilidade contextual" que explica que a classificação não pode depender apenas de um caractere após o outro, pois algumas regras são sensíveis ao contexto.
Observe também estes pontos em 1.8:
Mas o algoritmo por si só é um pouco denso. A essência disso é: Resumidamente, o Algoritmo de Agrupamento Unicode recebe uma string Unicode de entrada e uma Tabela de Elementos de Agrupamento, contendo dados de mapeamento para caracteres. Ele produz uma chave de classificação, que é uma matriz de inteiros de 16 bits sem sinal. Duas ou mais chaves de classificação assim produzidas podem ser comparadas binariamente para fornecer a comparação correta entre as strings para as quais foram geradas.
Você pode ver as regras de classificação latinas específicas aqui: http://developer.mimer.com/collations/charts/latin.htm ou mais direta e especificamente para MS SQL: http://collation-charts.org/mssql/mssql. 0409.1252.Latin1_General_CS_AS.html
Para o
e
personagem que mostra:Isso explica seus resultados ao fazer o pedido,
col1
exceto que ē não existe na página de código 1252, então não tenho absolutamente nenhuma ideia do que ele faz com ele.Ou se fizermos o algoritmo Unicode manualmente, usando o valor de chaves de DUCET em http://www.unicode.org/Public/UCA/latest/allkeys.txt :
passo 1: Formulário de normalização D, então cada caso se torna:
passo 2, produzir matrizes de agrupamento (pesquisa no arquivo
allkeys.txt
)Etapa 3, Chaves de classificação de formulário (para cada nível, pegue cada valor dentro de cada matriz de agrupamento, coloque 0000 como delimitador e comece novamente para o próximo nível)
passo 4, Comparar chaves de classificação (comparação binária simples de cada valor um por um): O quarto valor é suficiente para classificar todos, então a ordem final se torna:
Da mesma forma para pedidos em
col2
:passo 1: NFD
etapa 2: matrizes de agrupamento
step 3 : Form sort keys
step 4 : Compare sort keys: The second value is enough to sort them all, and it is in fact already in increasing order, so the final order is indeed:
Update: adding Solomon Rutzky third case, which is trickier because of the space that enables new rules (I chose the "non-ignorable case"):
step 1, NFD:
step 2, Produce collation arrays:
step 3, Form sort keys:
step 4, Compare sort keys:
Basically the third value determines the order, and it is in fact only based on the last digit, so the order should be:
Second update based on Solomon Rutzky's comment about Unicode versions.
I used the
allkeys.txt
data about latest Unicode version at this time, that is version 10.0If we need to take instead into account Unicode 5.1, this would be: http://www.unicode.org/Public/UCA/5.1.0/allkeys.txt
I just checked, for all the characters above, the collation arrays are the following instead:
and:
and:
which then compute to the following sorting keys:
and:
and:
which again gives these three sorted results:
and
and