Não é possível atualizar "CO2" para "CO₂" na linha da tabela


19

Dada esta tabela:

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');

Percebi que não posso corrigir um problema tipográfico:

SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO₂' WHERE id = 1;
SELECT * FROM test WHERE id = 1;

porque a atualização corresponde, mas não tem efeito:

id          description
----------- -----------
1           CO2

(1 affected rows)

(1 affected rows)

id          description
----------- -----------
1           CO2

(1 affected rows)

É como se o SQL Server determinasse que, como é obviamente apenas um pequeno 2 , o valor final não será alterado, portanto não vale a pena alterá-lo.

Alguém poderia lançar alguma luz sobre isso e talvez sugerir uma solução alternativa (que não seja a atualização para um valor intermediário)?


11
Álvaro: se você quiser aprender mais sobre esse comportamento, para entender melhor por que isso estava acontecendo, consulte os dois links que acabei de adicionar na parte inferior da minha resposta.
Solomon Rutzky

Respostas:


29

O subscrito 2 não faz parte do conjunto de caracteres varchar (em qualquer agrupamento, não apenas em Modern_Spanish). Portanto, faça uma constante nvarchar:

UPDATE test SET description = N'CO₂' WHERE id = 1;

11
Não só fixei o valor, como também entendi como chegou lá em primeiro lugar. Obrigado!
Álvaro González

2
@ ÁlvaroGonzález e gbn: Só para esclarecer, "Subscrito 2" não está disponível na Página de Código especificada pelo Collation padrão do Banco de Dados em questão, que é o Collation usado para literais e variáveis ​​de string, não o Collation da coluna (embora ambos pode estar usando a mesma página de código). No entanto, "Subscrito 2" está disponível na Página de Código 949 por meio dos Collations Coreanos. Isso não vai ajudar aqui, mas apenas para sua informação. Eu tenho detalhes e um exemplo na minha resposta .
Solomon Rutzky 23/11

21

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:

  1. Você está usando um VARCHARliteral (sem Nprefixo) em vez de um NVARCHARliteral (string com Nprefixo); portanto, o caractere Unicode será convertido em VARCHAR.
  2. 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.
  3. 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. VARCHARos 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 NVARCHARdados podem mapear pouco mais de 1,1 milhão de caracteres Unicode (embora pouco menos de 250k atualmente mapeados).
  4. Devido ao número limitado de mapeamentos que podem ser feitos com 8 bits / VARCHARdados, diferentes agrupamentos de caracteres (baseados em Idioma / Cultura) estão espalhados por várias "Páginas de Código" (ou seja, conjuntos de caracteres)
  5. Cada agrupamento especifica qual página de código, se houver, a ser usada para VARCHARdados ( NVARCHARsão todos caracteres)
  6. 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
  7. 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.
  8. 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 NVARCHARde VARCHARconversão devido à falta do Nprefixo 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 2vez 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 NVARCHARcoluna, 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 Nprefixo 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_SpanishCollations) para VARCHARdados, 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 VARCHARdados, 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:

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.