Невозможно обновить «CO2» до «CO₂» в строке таблицы


19

Учитывая эту таблицу:

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

(1 affected rows)

(1 affected rows)

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

(1 affected rows)

Как будто SQL Server определяет, что, поскольку obviously , очевидно, всего лишь крошечная 2 , конечное значение не изменится, поэтому его не стоит менять.

Может ли кто-то пролить некоторый свет на это и, возможно, предложить обходной путь (кроме обновления до промежуточного значения)?


1
Альваро: если вы хотите больше узнать об этом поведении, чтобы лучше понять, почему это происходит, посмотрите две ссылки, которые я только что добавил, внизу моего ответа.
Соломон Руцкий

Ответы:


29

Индекс 2 не является частью набора символов varchar (в любом сопоставлении, не только Modern_Spanish). Так что сделайте это константой nvarchar:

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

1
Мало того, что я установил значение, я также понял, как это вообще получилось. Спасибо!
Альваро Гонсалес

2
@ ÁlvaroGonzález и gbn: Просто чтобы прояснить, «Подстрочный индекс 2» недоступен на кодовой странице, указанной по умолчанию для сопоставления рассматриваемой базы данных, то есть для сопоставления, используемого для строковых литералов и переменных, а не для сопоставления столбца (хотя оба может использовать ту же кодовую страницу). Тем не менее, «Subscript 2» доступен в кодовой странице 949 через корейские подборки. Это не поможет, но только к вашему сведению. У меня есть детали и пример в моем ответе .
Соломон Руцкий

21

@gbn уже объяснил основную причину и исправление, но конкретная причина поведения, которое вы видите, заключается в следующем:

  1. Вы используете VARCHARлитерал (без Nпрефикса) вместо NVARCHARлитерала (строка с Nпрефиксом), поэтому символ Unicode будет преобразован в VARCHAR.
  2. VARCHARявляется 8-битной кодировкой, которая в большинстве случаев составляет один байт на символ, но также может составлять два байта на символ. С другой стороны, NVARCHARэто 16-битная кодировка (UTF-16 Little Endian), которая составляет либо два байта, либо четыре байта на символ.
  3. Из-за разницы в количестве доступных байтов, используемых для отображения символов, 8-битные кодировки по своей природе намного более ограничены в количестве символов, которые могут быть отображены. VARCHARданные могут содержать до 256 символов для однобайтовых наборов символов (большинство из них) и до 65 536 символов для двухбайтовых наборов символов (только некоторые из них). С другой стороны, NVARCHARданные могут отображать чуть более 1,1 миллиона символов Юникода (хотя в настоящее время сопоставлено чуть менее 250 тысяч символов).
  4. Из-за ограниченного числа отображений, которые могут быть сделаны с 8-битными VARCHARданными, различные группы символов (в зависимости от языка / культуры) распределены по нескольким «кодовым страницам» (то есть наборам символов)
  5. Каждое сопоставление указывает, какую кодовую страницу, если она есть, использовать для VARCHARданных ( NVARCHARвсе символы).
  6. При преобразовании строкового литерала или переменной из NVARCHAR(т. Е. Unicode / UTF-16 / все символы) в VARCHAR(набор символов, основанный на кодовой странице, которая указана в большинстве параметров сортировки), используется сортировка базы данных по умолчанию.
  7. Если кодовая страница сопоставления, используемого для преобразования, не содержит того же символа, но содержит отображение «наилучшего соответствия», то будет использовано отображение «наилучшего соответствия».
  8. Если кодовая страница сопоставления, используемого для преобразования, не содержит того же символа или содержит отображение «наилучшего соответствия», то будет использоваться символ «замены» по умолчанию (чаще всего ?).

Итак, что вы видите , является 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_SpanishCollations) для 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₂

ОБНОВИТЬ

Для тех, кто заинтересован в том, чтобы узнать больше о том , что именно здесь происходит (то есть все подробности), пожалуйста, посмотрите расследование из двух частей, которое я только что опубликовал:

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.