@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:
- Vous utilisez un
VARCHAR
littéral (pas de N
préfixe) au lieu d'un NVARCHAR
littéral (chaîne avec N
préfixe), donc le caractère Unicode sera converti en VARCHAR
.
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.
- 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'entre eux). D'un autre côté, les NVARCHAR
donné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).
- En raison du nombre limité de mappages pouvant être effectués avec 8 bits /
VARCHAR
donné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)
- Chaque classement spécifie la page de code, le cas échéant, à utiliser pour les
VARCHAR
données ( NVARCHAR
est tous les caractères)
- 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é
- 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é.
- 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 VARCHAR
conversion en raison de manquer le N
pré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 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 NVARCHAR
colonne, 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' N
absence 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_Spanish
classements) pour les VARCHAR
donné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 VARCHAR
donné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: