@gbn уже объяснил основную причину и исправление, но конкретная причина поведения, которое вы видите, заключается в следующем:
- Вы используете
VARCHAR
литерал (без N
префикса) вместо NVARCHAR
литерала (строка с N
префиксом), поэтому символ Unicode будет преобразован в VARCHAR
.
VARCHAR
является 8-битной кодировкой, которая в большинстве случаев составляет один байт на символ, но также может составлять два байта на символ. С другой стороны, NVARCHAR
это 16-битная кодировка (UTF-16 Little Endian), которая составляет либо два байта, либо четыре байта на символ.
- Из-за разницы в количестве доступных байтов, используемых для отображения символов, 8-битные кодировки по своей природе намного более ограничены в количестве символов, которые могут быть отображены.
VARCHAR
данные могут содержать до 256 символов для однобайтовых наборов символов (большинство из них) и до 65 536 символов для двухбайтовых наборов символов (только некоторые из них). С другой стороны, NVARCHAR
данные могут отображать чуть более 1,1 миллиона символов Юникода (хотя в настоящее время сопоставлено чуть менее 250 тысяч символов).
- Из-за ограниченного числа отображений, которые могут быть сделаны с 8-битными
VARCHAR
данными, различные группы символов (в зависимости от языка / культуры) распределены по нескольким «кодовым страницам» (то есть наборам символов)
- Каждое сопоставление указывает, какую кодовую страницу, если она есть, использовать для
VARCHAR
данных ( NVARCHAR
все символы).
- При преобразовании строкового литерала или переменной из
NVARCHAR
(т. Е. Unicode / UTF-16 / все символы) в VARCHAR
(набор символов, основанный на кодовой странице, которая указана в большинстве параметров сортировки), используется сортировка базы данных по умолчанию.
- Если кодовая страница сопоставления, используемого для преобразования, не содержит того же символа, но содержит отображение «наилучшего соответствия», то будет использовано отображение «наилучшего соответствия».
- Если кодовая страница сопоставления, используемого для преобразования, не содержит того же символа или содержит отображение «наилучшего соответствия», то будет использоваться символ «замены» по умолчанию (чаще всего
?
).
Итак, что вы видите , является NVARCHAR
для VARCHAR
преобразования из - за отсутствия в N
приставку на строковый литерал. Кроме того, кодовая страница сортировки по умолчанию для базы данных не содержит точно такой же символ, но было найдено отображение «наилучшего соответствия», поэтому вы получаете 2
вместо a ?
.
Вы можете увидеть этот эффект, выполнив следующий простой тест:
SELECT '₂', N'₂';
Возвращает:
2 ₂
Для ясности, если бы кодовая страница параметров сортировки по умолчанию для базы данных содержала точно такой же символ, то она была бы переведена в тот же символ в этой кодовой странице. И затем, в вашем случае, поскольку вы сохраняете данные в NVARCHAR
столбце, он снова перешел бы обратно к исходному символу Unicode. Последний пример ниже показывает это поведение.
ВАЖНО: Пожалуйста, имейте в виду, что преобразование происходит во время интерпретации строкового литерала, то есть до его сохранения в столбце. Это означает, что даже если столбец может содержать этот символ, он уже будет преобразован во что-то другое, в соответствии с параметром Сортировка базы данных по умолчанию, все из-за того, что N
прервался префикс этого строкового литерала. И это именно то, что вы (или были) испытывают.
Например, если в качестве параметров сортировки по умолчанию для вашей базы данных используется одно из корейских сопоставлений (один из четырех двухбайтовых наборов символов), то вы не увидели бы эту проблему, поскольку в этом символе доступен символ «Подстрочный индекс 2». набор (кодовая страница 949). Попробуйте следующий тест, чтобы увидеть (он использует Collation столбца вместо Collation по умолчанию для базы данных, поскольку это легче показать):
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;
Возвращает:
8bit_Latin1_General-1252 8bit_Korean-949 UTF16LE_Latin1_General-1252
2 ₂ ₂
Как вы можете видеть, Latin1_General Collations, которые используют кодовую страницу 1252 (ту же кодовую страницу, которую используют Modern_Spanish
Collations) для VARCHAR
данных, не имеют точного соответствия, но у них есть отображение «наилучшего соответствия» (то, что вы видите ). НО, корейские сопоставления, которые используют кодовую страницу 949 для VARCHAR
данных, имеют точное совпадение для символа «Подстрочный индекс 2».
Чтобы дополнительно проиллюстрировать это, мы можем создать новую базу данных с сопоставлением по умолчанию одного из корейских сопоставлений, а затем выполнить точный SQL, который находится в вопросе:
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;
Возвращает:
id description
1 CO2
id description
1 CO₂
ОБНОВИТЬ
Для тех, кто заинтересован в том, чтобы узнать больше о том , что именно здесь происходит (то есть все подробности), пожалуйста, посмотрите расследование из двух частей, которое я только что опубликовал: