O @gbn já explicou o motivo básico e a correção, mas o motivo específico do comportamento que você está vendo é o seguinte:
- Você está usando um
VARCHAR
literal (sem N
prefixo) em vez de um NVARCHAR
literal (string com N
prefixo); portanto, o caractere Unicode será convertido em VARCHAR
.
VARCHAR
é uma codificação de 8 bits que é, na maioria dos casos, um byte por caractere, mas também pode ter dois bytes por caractere. Por outro lado, NVARCHAR
é uma codificação de 16 bits (UTF-16 Little Endian) que tem dois bytes ou quatro bytes por caractere.
- Devido à diferença no número de bytes disponíveis para o mapeamento de caracteres, as codificações de 8 bits são, por natureza, muito mais limitadas no número de caracteres que podem ser mapeados.
VARCHAR
os dados têm até 256 caracteres para conjuntos de caracteres de byte único (a maioria deles) e até 65.536 caracteres para conjuntos de caracteres de byte duplo (apenas alguns deles). Por outro lado, os NVARCHAR
dados podem mapear pouco mais de 1,1 milhão de caracteres Unicode (embora pouco menos de 250k atualmente mapeados).
- Devido ao número limitado de mapeamentos que podem ser feitos com 8 bits /
VARCHAR
dados, diferentes agrupamentos de caracteres (baseados em Idioma / Cultura) estão espalhados por várias "Páginas de Código" (ou seja, conjuntos de caracteres)
- Cada agrupamento especifica qual página de código, se houver, a ser usada para
VARCHAR
dados ( NVARCHAR
são todos caracteres)
- Ao converter uma string literal ou variável de
NVARCHAR
(por exemplo, Unicode / UTF-16 / all caracteres) para VARCHAR
(conjunto de caracteres baseado na página de código especificada na maioria dos agrupamentos), o agrupamento padrão do banco de dados é usado
- Se a Página de Código do Agrupamento usada para a conversão não contiver o mesmo caractere, mas contiver um mapeamento de "melhor ajuste", o mapeamento de "melhor ajuste" será usado.
- Se a Página de Código do agrupamento que está sendo usada para a conversão não contiver o mesmo caractere ou um mapeamento de "melhor ajuste", o caractere "substituto" padrão será usado (geralmente
?
).
Então, o que você está vendo é uma NVARCHAR
de VARCHAR
conversão devido à falta do N
prefixo sobre o literal string. E a página de código do agrupamento padrão para o banco de dados não contém exatamente o mesmo caractere, mas um mapeamento de "melhor ajuste" foi encontrado, e é por isso que você está recebendo um em 2
vez de um ?
.
Você pode ver esse efeito fazendo o seguinte teste simples:
SELECT '₂', N'₂';
Devoluções:
2 ₂
Para ficar claro, se a página de código do agrupamento padrão para o banco de dados contivesse exatamente o mesmo caractere, ela seria traduzida no mesmo caractere nessa página de código. E, no seu caso, como você está armazenando em uma NVARCHAR
coluna, ela seria traduzida novamente, de volta ao caractere Unicode original. O exemplo final abaixo mostra esse comportamento.
IMPORTANTE: Lembre-se de que a conversão ocorre quando a literal da cadeia está sendo interpretada, ou seja, antes de ser armazenada na coluna. Isso significa que, mesmo que a coluna possa conter esse caractere, ela já terá sido convertida em outra coisa, com base no agrupamento padrão do banco de dados, tudo devido a deixar de fora o N
prefixo dessa literal de cadeia de caracteres. E é exatamente isso que você está (ou estava) experimentando.
Por exemplo, se o agrupamento padrão do seu banco de dados fosse um dos agrupamentos coreanos (um dos quatro conjuntos de caracteres de byte duplo), você não perceberia esse problema, pois o caractere "Subscrito 2" está disponível nesse caractere (Página de código 949). Tente o seguinte teste para ver (ele usa o agrupamento da coluna em vez do agrupamento padrão do banco de dados, como é mais fácil de mostrar):
CREATE TABLE #TestChar
(
[8bit_Latin1_General-1252] VARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC,
[8bit_Korean-949] VARCHAR(2) COLLATE Korean_100_CI_AS_SC,
[UTF16LE_Latin1_General-1252] NVARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC
);
INSERT INTO #TestChar VALUES (N'₂', N'₂', N'₂');
SELECT * FROM #TestChar;
Devoluções:
8bit_Latin1_General-1252 8bit_Korean-949 UTF16LE_Latin1_General-1252
2 ₂ ₂
Como você pode ver, os Latin1_General Collations, que usam a Página de Código 1252 (a mesma Página de Código que os Modern_Spanish
Collations) para VARCHAR
dados, não têm uma correspondência exata, mas têm um mapeamento de "melhor ajuste" (que é o que você está vendo) ) MAS, os agrupamentos coreanos, que usam o Código Página 949 para VARCHAR
dados, têm uma correspondência exata para o caractere "Subscrito 2".
Para ilustrar melhor, podemos criar um novo banco de dados com um agrupamento padrão de um dos agrupamentos coreanos e, em seguida, executar o SQL exato que está na pergunta:
CREATE DATABASE [TestKorean-949] COLLATE Korean_100_CI_AS_KS_WS_SC;
ALTER DATABASE [TestKorean-949] SET RECOVERY SIMPLE;
GO
USE [TestKorean-949];
CREATE TABLE test (
id INT NOT NULL,
description NVARCHAR(100) COLLATE Modern_Spanish_CI_AS NOT NULL
);
INSERT INTO test (id, description) VALUES (1, 'CO2');
SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO₂' WHERE id = 1;
SELECT * FROM test WHERE id = 1;
Devoluções:
id description
1 CO2
id description
1 CO₂
ATUALIZAR
Para quem estiver interessado em descobrir mais sobre o que exatamente está acontecendo aqui (ou seja, todos os detalhes sangrentos), consulte a investigação em duas partes que acabei de publicar: