Impossible de mettre à jour «CO2» en «CO₂» dans la ligne du tableau


19

Compte tenu de ce tableau:

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

J'ai réalisé que je ne peux pas résoudre un problème typographique:

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

car la mise à jour correspond mais n'a aucun effet:

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

(1 affected rows)

(1 affected rows)

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

(1 affected rows)

C'est comme si SQL Server détermine que, puisque n'est évidemment qu'un minuscule 2 , la valeur finale ne changera pas, donc cela ne vaut pas la peine de la changer.

Quelqu'un pourrait-il éclairer cela et peut-être suggérer une solution de contournement (autre que la mise à jour vers une valeur intermédiaire)?


1
Álvaro: si vous voulez en savoir plus sur ce comportement, pour mieux comprendre pourquoi cela se produit, veuillez consulter les deux liens que je viens d'ajouter au bas de ma réponse.
Solomon Rutzky

Réponses:


29

L'indice 2 ne fait pas partie du jeu de caractères varchar (dans n'importe quel classement, pas seulement Modern_Spanish). Faites-en une constante nvarchar:

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

1
Non seulement j'ai fixé la valeur, mais j'ai également compris comment cela est arrivé en premier lieu. Je vous remercie!
Álvaro González

2
@ ÁlvaroGonzález et gbn: Juste pour être clair, "Subscript 2" n'est pas disponible dans la page de code spécifiée par le classement par défaut de la base de données en question, qui est le classement utilisé pour les littéraux de chaîne et les variables, pas le classement de la colonne (bien que les deux pourrait utiliser la même page de codes). Toutefois, "Subscript 2" est disponible dans la page de code 949 via le coréen Collations. Cela n'aidera pas ici, mais juste pour info. J'ai des détails et un exemple dans ma réponse .
Solomon Rutzky

21

@gbn a déjà expliqué la raison de base et le correctif, mais la raison spécifique du comportement que vous voyez est la suivante:

  1. Vous utilisez un VARCHARlittéral (pas de Npréfixe) au lieu d'un NVARCHARlittéral (chaîne avec Npréfixe), donc le caractère Unicode sera converti en VARCHAR.
  2. VARCHARest un codage 8 bits qui est, dans la plupart des cas, un octet par caractère, mais peut également être de deux octets par caractère. D'autre part, NVARCHARest un codage 16 bits (UTF-16 Little Endian) qui est soit deux octets soit quatre octets par caractère.
  3. En raison de la différence du nombre d'octets disponibles à utiliser pour le mappage de caractères, les codages 8 bits sont, par leur nature même, beaucoup plus limités dans le nombre de caractères pouvant être mappés. VARCHARles données contiennent jusqu'à 256 caractères pour les jeux de caractères à un octet (la majorité d'entre eux) et jusqu'à 65 536 caractères pour les jeux de caractères à deux octets (seulement quelques-uns d'entre eux). D'un autre côté, les NVARCHARdonnées peuvent mapper un peu plus de 1,1 million de caractères Unicode (bien qu'un peu moins de 250 000 soient actuellement mappés).
  4. En raison du nombre limité de mappages pouvant être effectués avec 8 bits / VARCHARdonnées, différents regroupements de caractères (basés sur la langue / la culture) sont répartis sur plusieurs "pages de code" (c'est-à-dire des jeux de caractères)
  5. Chaque classement spécifie la page de code, le cas échéant, à utiliser pour les VARCHARdonnées ( NVARCHARest tous les caractères)
  6. Lors de la conversion d'un littéral de chaîne ou d'une variable de NVARCHAR(c.-à-d. Unicode / UTF-16 / tous les caractères) en VARCHAR(jeu de caractères basé sur la page de codes qui est spécifiée dans la plupart des classements), le classement par défaut de la base de données est utilisé
  7. Si la page de codes du classement utilisé pour la conversion ne contient pas le même caractère, mais contient un mappage "meilleur ajustement", alors le mappage "le mieux adapté" sera utilisé.
  8. Si la page de codes du classement utilisé pour la conversion ne contient pas le même caractère ou ne contient pas de mappage «optimal», le caractère de «remplacement» par défaut sera utilisé (le plus souvent ?).

Donc, ce que vous voyez est NVARCHARà la VARCHARconversion en raison de manquer le Npréfixe littéral de chaîne. Et, la page de codes du classement par défaut pour la base de données ne contient pas exactement le même caractère, mais un mappage "meilleur ajustement" a été trouvé, c'est pourquoi vous obtenez un 2au lieu d'un ?.

Vous pouvez voir cet effet en effectuant le test simple suivant:

SELECT '₂', N'₂';

Retour:

2    ₂

Pour être clair, SI la page de codes du classement par défaut pour la base de données contenait exactement le même caractère, alors elle aurait été traduite dans le même caractère dans cette page de codes. Et puis, dans votre cas, puisque vous stockez dans une NVARCHARcolonne, il se serait traduit à nouveau, en retour au caractère Unicode d'origine. Le dernier exemple ci-dessous montre ce comportement.

IMPORTANT: veuillez noter que la conversion se produit lorsque le littéral de chaîne est interprété, c'est-à-dire avant d' être stocké dans la colonne. Cela signifie que même si la colonne peut contenir ce caractère, il aura déjà été converti en quelque chose d'autre, basé sur le classement par défaut de la base de données, tout cela en raison de l' Nabsence du préfixe sur cette chaîne littérale. Et c'est exactement ce que vous vivez (ou viviez).

Par exemple, si le classement par défaut de votre base de données aurait été l'un des classements coréens (l'un des quatre jeux de caractères codés sur deux octets), vous n'auriez pas vu ce problème car le caractère "Indice 2" est disponible dans ce caractère. (page de codes 949). Essayez le test suivant pour voir (il utilise le classement de la colonne au lieu du classement par défaut de la base de données car c'est plus facile à afficher):

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;

Retour:

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

Comme vous pouvez le voir, les classements Latin1_General, qui utilisent la page de codes 1252 (la même page de codes que les Modern_Spanishclassements) pour les VARCHARdonnées, n'ont pas de correspondance exacte, mais ils ont un mappage "le mieux adapté" (c'est ce que vous voyez ). MAIS, les collations coréennes, qui utilisent la page de codes 949 pour les VARCHARdonnées, ont une correspondance exacte pour le caractère "indice 2".


Pour illustrer davantage, nous pouvons créer une nouvelle base de données avec un classement par défaut de l'un des classements coréens, puis exécuter le SQL exact qui se trouve dans la question:

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;

Retour:

id  description
1   CO2


id  description
1   CO₂

MISE À JOUR

Pour quiconque souhaite en savoir plus sur ce qui se passe exactement ici (c'est-à-dire tous les détails sanglants), veuillez consulter l'enquête en deux parties que je viens de publier:

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.