Eu tenho um problema com os caracteres Сyrillic 'E' e 'e', que devem ser iguais quando um agrupamento que não diferencia maiúsculas de minúsculas é usado. E isso é correto para todos os agrupamentos, exceto os cazaques.
Eu verifiquei todos os agrupamentos de CI usando uma consulta abaixo:
SELECT 'DECLARE @Test_' + name + ' TABLE (
Code nvarchar(32) COLLATE ' + name + '
)
INSERT @Test_' + name + ' (Code)
VALUES (N''Е''), (N''е'')
SELECT ''' + name + ''', * FROM @Test_' + name + ' WHERE Code = N''Е'' COLLATE ' + name
FROM sys.fn_helpcollations()
WHERE name LIKE '%CI%'
Duas linhas são retornadas conforme o esperado para todos os agrupamentos, mas não para o cazaque.
Exemplo de consulta para ilustrar o problema:
SET NOCOUNT ON;
DECLARE @Test TABLE (
Code nvarchar(32) COLLATE Kazakh_90_CI_AS
);
DECLARE @UpperChar nchar(1) = N'Е';
DECLARE @LowerChar nchar(1) = N'е';
SELECT ASCII(@UpperChar) AS 'UpperChar ASCII', ASCII(@LowerChar) AS 'LowerChar ASCII';
/* Just ASCII-codes for the chars
UpperChar ASCII LowerChar ASCII
--------------- ---------------
197 229
*/
INSERT @Test (Code)
VALUES (@UpperChar), (@LowerChar);
SELECT DISTINCT Code AS 'DISTINCT Code' FROM @Test;
/* Should be one row with CI collation - FALSE
DISTINCT Code
--------------------------------
Е
е
*/
SELECT Code AS 'Code = @UpperChar'
FROM @Test
WHERE Code = @UpperChar;
/* Should be two rows with CI collation - FALSE
Code = @UpperChar
--------------------------------
Е
*/
SELECT Code AS 'Code = @LowerChar'
FROM @Test
WHERE Code = @LowerChar;
/* Should be two rows with CI collation - FALSE
Code = @LowerChar
--------------------------------
е
*/
SELECT Code AS 'Code = @UpperChar OR Code = LOWER(@UpperChar)'
FROM @Test
WHERE Code = @UpperChar
OR Code = LOWER(@UpperChar);
/*Check LOWER('Е') = 'е' - TRUE
Code = @UpperChar OR Code = LOWER(@UpperChar)
---------------------------------------------
Е
е
*/
SELECT Code AS 'Code = @LowerChar OR Code = UPPER(@LowerChar)'
FROM @Test
WHERE Code = @LowerChar
OR Code = UPPER(@LowerChar);
/*Check UPPER('е') = 'Е' - TRUE
Code = @LowerChar OR Code = UPPER(@LowerChar)
---------------------------------------------
Е
е
*/
Outros caracteres Сyrillic se comportam conforme o esperado.
O que eu poderia fazer para corrigir o problema?
Antes de entrarmos nos detalhes, aqui estão duas coisas que ajudariam em geral:
ASCII()
função destina-se aVARCHAR
dados e é sensível à página de código associada ao agrupamento dos dados (para variáveis, é o agrupamento padrão do banco de dados atual). Mas, neste caso, estamos lidando apenas com Unicode/NVARCHAR
dados, portanto, aUNICODE()
função deve ser usada.CHAR()
ouNCHAR()
. Isso torna o script mais transportável, pois não haverá problemas de conversão de caracteres ao abrir/colar o script em um ambiente que não oferece suporte a determinados caracteres. E torna o script mais legível/compreensível, pois não haverá confusão para os leitores ao trabalhar com personagens que se parecem com outros personagens, mas são de fato diferentes (como os que estamos lidando aqui).Agora, um pouco de fundo primeiro para que a explicação faça sentido:
A classificação/comparação Unicode é feita atribuindo vários pesos a cada caractere. Duas das categorias de peso são maiúsculas e minúsculas (isto é, acentos). Ter várias categorias torna mais fácil lidar com as várias combinações de maiúsculas e minúsculas x insensíveis e de acentos x insensíveis. A maioria (se não todos) os caracteres definidos têm pesos de classificação padrão. Esses pesos padrão podem ser substituídos por valores específicos de cultura ao usar uma cultura/localidade específica. Ao usar o inglês dos EUA, os padrões são usados (ou seja, sem substituições). É por isso que os caracteres de outros idiomas ainda são classificados corretamente (ou quase corretamente) mesmo ao usar um
Latin1_General
agrupamento e porque o inglês dos EUA ainda funcionará corretamente ao usar umHebrew
(ouJapanese
, etc) agrupamento (porque oHebrew
o agrupamento não substitui os pesos dos caracteres do inglês americano).Os vários pesos para cada caractere são mantidos em um arquivo. Os mapeamentos de maiúsculas e minúsculas estão em um arquivo separado. E, embora o Consórcio Unicode tenha entrado no ritmo de atualização anual, a Microsoft o faz com menos frequência. Com base em seus arquivos de peso de classificação disponíveis publicamente , eles têm apenas as seguintes versões (para Windows):
O SQL Server tem menos versões:
_90_
em seu nome foram introduzidos no SQL Server 2005_100_
em seu nome foram introduzidos no SQL Server 2008_140_
o nome foram introduzidos no SQL Server 2017 (infelizmente, os únicos agrupamentos com esta versão são os agrupamentos japoneses)Por fim, lembre-se de que os agrupamentos do SQL Server são baseados , mas não são idênticos aos agrupamentos do Windows. Acredito que os agrupamentos da versão 100 estão associados ao arquivo "Windows Server 2008", enquanto os agrupamentos das versões 80 e 90 devem estar mais intimamente associados ao arquivo "Windows NT 4.0 até o Windows Server 2003" (desde que o Vista foi lançado em 2007) .
Com tudo isso em mente:
Só para não haver confusão para quem está lendo isso: os caracteres em questão aqui são "Cyrillic Maiúscula Ie: Е" e "Cyrillic Small Letter Ie: е" (pontos de código Unicode U+0415 e U+0435, respectivamente), que parecem idênticos aos caracteres latinos "E" e "e", mas são definitivamente diferentes. Por exemplo:
retorna:
O comportamento padrão em todos os arquivos de peso de classificação da Microsoft é que esses dois caracteres são os mesmos, exceto pelo caso. É por isso que eles comparam como iguais em todos os agrupamentos que não diferenciam maiúsculas de minúsculas, exceto os
Kazakh
agrupamentos.Por que esses dois caracteres — U+0415 e U+0435 — são comparados como diferentes ao usar agrupamentos cazaques que não diferenciam maiúsculas de minúsculas? Porque (e por qualquer motivo), os arquivos de peso de classificação "Windows NT 4.0 até Windows Server 2003" e "Windows Server 2008" contêm uma substituição para U+0435 ("Cyrillic Small Letter Ie: е") ao usar o cazaque cultura. A substituição faz com que este caractere, U+0435, seja igual aos seguintes caracteres (e alguns outros):
As substituições específicas do Cazaquistão também fazem com que o caractere "Cyrillic Small Letter Io" (U+0451) não seja mais igual ao seu caractere maiúsculo:
Todo esse comportamento é remanescente da implementação inicial do Unicode da Microsoft (lá no Windows NT 4.0!). A Microsoft, para seu crédito, foi uma das primeiras a adotar o Unicode, e esse comportamento podeser como foi definido no Unicode versão 1.0. É difícil dizer com certeza, pois não é fácil encontrar os arquivos de agrupamento originais para Unicode (acho que a versão 2.1 foi a mais antiga que pude encontrar). No entanto, posso dizer, observando os arquivos fornecidos pela Microsoft, embora eles tenham atualizado os pesos de classificação padrão e adicionado caracteres e culturas em cada um desses arquivos, algumas definições, como as substituições específicas do Cazaquistão, não foram atualizadas até mais recentemente. . Na verdade, não é até o arquivo "Windows 8 e Windows Server 2012" (ou seja, o segundo conjunto de definições mais recente) que eles corrigem as substituições específicas do Cazaquistão (e presumo que outras).
Portanto, embora o Windows em geral (e possivelmente também o .NET) manipule corretamente os agrupamentos do Cazaquistão (começando com o Windows 8 e o Windows Server 2012), os agrupamentos do SQL Server ainda estão presos no passado. Ou seja, não há correção fora de um milagre que a Microsoft atualize esses agrupamentos para uma versão mais recente do Unicode. Eu tenho uma ideia que estou pensando em propor a eles como torná-la ainda melhor do que uma simples atualização de definição, então talvez eu deva prosseguir e enviar isso (atualizarei esta resposta com o link assim que terminar que).
Mas, por enquanto, se você realmente precisa que esses dois caracteres sejam iguais em um agrupamento cazaque sem distinção entre maiúsculas e minúsculas (e não se esqueça de "Cyrillic Small Letter Io" (U+0451)), você terá que fazer o truque do seu
UPPER()
exemplo (embora eu não possa dizer com certeza que essa solução alternativa não causará nenhum problema).Para obter mais informações sobre agrupamentos, Unicode e codificações, visite meu site: Collations.Info