web-dev-qa-db-fra.com

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

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 tout petit 2, la valeur finale ne changera donc pas la peine de la changer.

Quelqu'un pourrait-il faire la lumière sur ce point et peut-être suggérer une solution de contournement (autre que la mise à jour vers une valeur intermédiaire)?

19
Álvaro González

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;
29
gbn

@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 littéral VARCHAR (pas de préfixe N) au lieu d'un littéral NVARCHAR (chaîne avec préfixe N), donc le caractère Unicode sera converti dans VARCHAR.
  2. VARCHAR est 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, NVARCHAR est 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. VARCHAR les 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'un autre côté, les données NVARCHAR peuvent mapper un peu plus de 1,1 million de caractères Unicode (bien qu'un peu moins de 250k soient actuellement mappés).
  4. En raison du nombre limité de mappages pouvant être effectués avec des données 8 bits/VARCHAR, 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 codes, le cas échéant, à utiliser pour les données VARCHAR (NVARCHAR est tous les caractères)
  6. Lors de la conversion d'un littéral de chaîne ou d'une variable de NVARCHAR (c'est-à-dire Unicode/UTF-16/tous les caractères) en VARCHAR (jeu de caractères basé sur la page de codes spécifiée dans la plupart des classements), le classement par défaut de la base de données est utilisée
  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 "meilleur ajustement" 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 une conversion NVARCHAR en VARCHAR car il manque le préfixe N sur le 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 2 au 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 colonne NVARCHAR, 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é, ce qui est avant il est 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, sur la base du classement par défaut de la base de données, tout cela en raison de la suppression du préfixe N sur ce littéral de chaîne. 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 (même page de codes que le Modern_Spanish Les classements utilisent) pour les données VARCHAR, n'ont pas de correspondance exacte, mais ils ont un mappage "le mieux adapté" (ce que vous voyez). MAIS, les collations coréennes, qui utilisent la page de codes 949 pour les données VARCHAR, 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 tous ceux qui sont intéressés à en savoir plus sur ce qui se passe ici exactement (c'est-à-dire tous les détails sanglants), veuillez consulter l'enquête en deux parties que je viens de publier:

21
Solomon Rutzky