No se puede actualizar "CO2" a "CO₂" en la fila de la tabla


19

Dada esta tabla:

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

Me di cuenta de que no puedo solucionar un problema tipográfico:

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

porque la actualización coincide pero no tiene efecto:

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

(1 affected rows)

(1 affected rows)

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

(1 affected rows)

Es como si SQL Server determina que, dado que obviamente es solo un pequeño 2 , el valor final no cambiará, por lo que no vale la pena cambiarlo.

¿Podría alguien arrojar algo de luz sobre esto y tal vez sugerir una solución alternativa (que no sea actualizar a un valor intermedio)?


1
Álvaro: si desea obtener más información sobre este comportamiento, para comprender mejor por qué sucedía esto, consulte los dos enlaces que acabo de agregar al final de mi respuesta.
Solomon Rutzky

Respuestas:


29

El subíndice 2 no forma parte del conjunto de caracteres varchar (en cualquier intercalación, no solo Modern_Spanish). Así que conviértalo en una constante nvarchar:

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

1
No solo fijé el valor, sino que también entendí cómo llegó allí en primer lugar. ¡Gracias!
Álvaro González

2
@ ÁlvaroGonzález y gbn: Para ser claros, el "Subíndice 2" no está disponible en la Página de códigos especificada por la Clasificación predeterminada de la base de datos en cuestión, que es la Clasificación utilizada para literales de cadena y variables, no la Clasificación de la columna (aunque ambas podría estar usando la misma página de códigos). Sin embargo, el "Subíndice 2" está disponible en la página de códigos 949 a través de las intercalaciones coreanas. Eso no ayudará aquí, pero solo para tu información. Tengo detalles y un ejemplo en mi respuesta .
Solomon Rutzky

21

@gbn ya explicó la razón básica y la solución, pero la razón específica del comportamiento que está viendo es esta:

  1. Está utilizando un VARCHARliteral (sin Nprefijo) en lugar de un NVARCHARliteral (cadena con Nprefijo), por lo tanto, el carácter Unicode se convertirá en VARCHAR.
  2. VARCHARes una codificación de 8 bits que, en la mayoría de los casos, es un byte por carácter, pero también puede tener dos bytes por carácter. Por otro lado, NVARCHARes una codificación de 16 bits (UTF-16 Little Endian) que tiene dos bytes o cuatro bytes por carácter.
  3. Debido a la diferencia en el número de bytes disponibles para usar para asignar caracteres, las codificaciones de 8 bits son, por su propia naturaleza, mucho más limitadas en el número de caracteres que se pueden asignar. VARCHARlos datos son de hasta 256 caracteres para juegos de caracteres de un solo byte (la mayoría de ellos) y hasta 65.536 caracteres para juegos de caracteres de doble byte (solo algunos de estos). Por otro lado, los NVARCHARdatos pueden mapear un poco más de 1.1 millones de caracteres Unicode (aunque actualmente menos de 250k mapeados).
  4. Debido a la cantidad limitada de asignaciones que se pueden hacer con 8 bits / VARCHARdatos, se agrupan diferentes agrupaciones de caracteres (en función del idioma / cultura) en varias "páginas de códigos" (es decir, conjuntos de caracteres)
  5. Cada clasificación especifica qué página de códigos, si hay alguna, usar para los VARCHARdatos ( NVARCHARson todos los caracteres)
  6. Al convertir un literal de cadena o variable de NVARCHAR(es decir, Unicode / UTF-16 / todos los caracteres) a VARCHAR(conjunto de caracteres basado en la página de códigos que se especifica en la mayoría de las intercalaciones), se utiliza la intercalación predeterminada de la base de datos
  7. Si la página de códigos de la clasificación utilizada para la conversión no contiene el mismo carácter, pero contiene una asignación de "mejor ajuste", se utilizará la asignación de "mejor ajuste".
  8. Si la página de códigos de la clasificación utilizada para la conversión no contiene el mismo carácter o contiene una asignación de "mejor ajuste", entonces se usará el carácter de "reemplazo" predeterminado (más comúnmente ?).

Por lo tanto, lo que está viendo es una NVARCHARde VARCHARconversión debido a que falta el Nprefijo en el literal de cadena. Y, la página de códigos de la clasificación predeterminada para la base de datos no contiene exactamente el mismo carácter, pero se encontró una asignación de "mejor ajuste", razón por la cual está obteniendo un en 2lugar de un ?.

Puede ver este efecto haciendo la siguiente prueba simple:

SELECT '₂', N'₂';

Devoluciones:

2    ₂

Para ser claros, SI la página de códigos de la clasificación predeterminada para la base de datos contenía exactamente el mismo carácter, entonces se habría traducido al mismo carácter en esa página de códigos. Y, luego, en su caso, dado que está almacenando en una NVARCHARcolumna, se habría traducido nuevamente, al carácter original de Unicode. El último ejemplo a continuación muestra este comportamiento.

IMPORTANTE: Tenga en cuenta que la conversión se produce cuando se interpreta el literal de cadena, que es antes de que se almacene en la columna. Esto significa que incluso si la columna puede contener ese carácter, ya se habrá convertido en otra cosa, en función de la Clasificación predeterminada de la Base de datos, todo debido a que omite el Nprefijo en ese literal de cadena. Y esto es exactamente lo que estás (o estabas) experimentando.

Por ejemplo, si la clasificación predeterminada de su base de datos hubiera sido una de las clasificaciones coreanas (uno de los cuatro conjuntos de caracteres de doble byte), entonces no habría visto este problema ya que el carácter "Subíndice 2" está disponible en ese carácter conjunto (página de códigos 949). Pruebe la siguiente prueba para ver (utiliza la Clasificación de la columna en lugar de la Clasificación predeterminada de la base de datos, ya que es más 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;

Devoluciones:

8bit_Latin1_General-1252    8bit_Korean-949    UTF16LE_Latin1_General-1252
2                           ₂                  ₂

Como puede ver, las colaciones Latin1_General, que usan la página de códigos 1252 (la misma página de códigos que Modern_Spanishusan las colaciones) para los VARCHARdatos, no tienen una coincidencia exacta, pero tienen una asignación de "mejor ajuste" (que es lo que está viendo) ) PERO, las colaciones coreanas, que usan la página de códigos 949 para VARCHARdatos, tienen una coincidencia exacta para el carácter "Subíndice 2".


Para ilustrarlo más, podemos crear una nueva Base de datos con una Clasificación predeterminada de una de las Clasificaciones coreanas, y luego ejecutar el SQL exacto que está en la pregunta:

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;

Devoluciones:

id  description
1   CO2


id  description
1   CO₂

ACTUALIZAR

Para cualquiera que esté interesado en obtener más información sobre lo que está sucediendo exactamente aquí (es decir, todos los detalles sangrientos), consulte la investigación en dos partes que acabo de publicar:

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.