Неможливо оновити "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 визначає, що, оскільки , очевидно, лише крихітний 2 , остаточне значення не зміниться, тому змінювати його не варто.

Чи може хтось пролити щось на це і, можливо, запропонує рішення (крім оновлення до посередницького значення)?


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

Відповіді:


29

Підписка 2 не є частиною набору символів varchar (у будь-якому зіставленні, не лише Modern_Spanish). Тому зробіть це nvarchar постійною:

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

1
Я не тільки зафіксував значення, але й зрозумів, як воно потрапило в першу чергу. Дякую!
Альваро Гонсалес

2
@ ÁlvaroGonzález та gbn: Для того, щоб було зрозуміло, "Підписка 2" недоступна на сторінці коду, визначеному зіставленням відповідної бази даних за замовчуванням, яка є зіставленням, що використовується для рядкових літералів та змінних, а не зіставленням стовпця (хоча обидва може використовуватися та ж сторінка коду). Однак "Підписка 2" доступна в Code Page 949 через Корейські зібрання. Це не допоможе тут, а лише FYI. У мене є деталі і приклад в моєму відповіді .
Соломон Руцький

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 мільйона символів Unicode (хоча трохи менше 250 000).
  4. Через обмежену кількість відображень, які можна виконати за допомогою 8-бітових / VARCHARданих, різні групи символів (на основі мови / культури) розкидаються по декількох "сторінках коду" (тобто наборах символів)
  5. Кожне зіставлення визначає, яку кодову сторінку, якщо вона є, використовувати для VARCHARданих ( NVARCHARце всі символи)
  6. При перетворенні рядкового буквеного або змінної з NVARCHAR(тобто Unicode / UTF-16 / всі символи) в VARCHAR(набір символів на основі кодової сторінки, яка вказана в більшості зібрань), використовується за замовчуванням зіставлення бази даних
  7. Якщо сторінка коду зіставлення, що використовується для перетворення, не містить того самого символу, але містить відображення "найкращого підходу", то буде використано відображення "найкращого пристосування".
  8. Якщо сторінка коду зіставлення, що використовується для перетворення, не містить того самого символу або містить відображення "найкращого підходу", тоді використовується символ "заміна" за замовчуванням (найчастіше ?).

Отже, що ви бачите , є NVARCHARдля VARCHARперетворення з - за відсутності в Nприставку на строковий літерал. І, Кодова сторінка за замовчуванням Збір за базою даних не містить точно такого ж символу, але було знайдено "найкраще" відображення, саме тому ви отримуєте 2замість ?.

Ви можете побачити цей ефект, виконавши такий простий тест:

SELECT '₂', N'₂';

Повернення:

2    ₂

Щоб було зрозуміло, якби кодова сторінка зібраного за замовчуванням зіставлення баз даних містила абсолютно той самий символ, то вона була б переведена на той самий символ на цій кодовій сторінці. І, тоді, у вашому випадку, оскільки ви зберігаєте в NVARCHARстовпчик, він переклав би знову, до початкового символу Unicode. Останній приклад нижче показує таку поведінку.

ВАЖЛИВО: Будь ласка, майте на увазі, що перетворення відбувається під час інтерпретації рядкового літералу, який є перед його збереженням у стовпчик. Це означає, що навіть якщо стовпець може містити цей символ, він уже буде перетворений на щось інше, грунтуючись на зіставленні бази даних за замовчуванням, все через відмову від Nпрефікса у цьому рядковому літералі. І саме це ви переживаєте (або були).

Наприклад, якщо зіставлення вашої бази даних за замовчуванням було б одним із корейських зібрань (один із чотирьох наборів символів з двома байтами), ви б не бачили цієї проблеми, оскільки в цьому символі доступний символ "Підписка 2". набір (Код, сторінка 949). Спробуйте переконатися в наступному тесті (він використовує зіставлення стовпця замість зіставлення бази даних за замовчуванням, як це простіше показати):

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, які використовують код даних для сторінки даних (Кодова сторінка Modern_Spanish) VARCHAR, не відповідають точній відповідності, але вони мають "найкраще" відображення (що саме ви бачите ). АЛЕ, корейські зібрання, які використовують код 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.