web-dev-qa-db-ja.com

テーブル行の「CO2」を「CO₂」に更新できません

この表を考えると:

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)

は明らかに小さな2であるため、SQL Serverがそれを決定したかのように、最終的な値は変更されないため、変更する価値はありません。

誰かがこれにいくつかの光を当て、多分(中間値に更新する以外の)回避策を提案できますか?

19

下付き文字2はvarchar文字セットの一部ではありません(どの照合でも、Modern_Spanishだけではありません)。だからそれをnvarchar定数にします:

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

@gbnはすでに基本的な理由と修正を説明していますが、表示されている動作の具体的な理由は次のとおりです。

  1. VARCHARリテラル(Nプレフィックス付きの文字列)の代わりにNVARCHARリテラル(Nプレフィックスなし)を使用しているため、Unicode文字が変換されますVARCHARに入れます。
  2. VARCHARは8ビットエンコーディングで、ほとんどの場合、1文字あたり1バイトですが、1文字あたり2バイトにすることもできます。一方、NVARCHARは1文字あたり2バイトまたは4バイトの16ビットエンコーディング(UTF-16リトルエンディアン)です。
  3. 文字のマッピングに使用できる使用可能なバイト数の違いにより、8ビットエンコーディングは、その性質上、マッピングできる文字数がはるかに制限されています。 VARCHARデータは、シングルバイト文字セット(その大部分)で最大256文字、ダブルバイト文字セット(これらの一部のみ)で最大65,536文字です。一方、NVARCHARデータは、110万個を超えるUnicode文字をマップできます(現在、25万個未満がマップされています)。
  4. 8ビット/ VARCHARデータで実行できるマッピングの数が限られているため、異なる言語のグループ(言語/文化に基づく)が複数の「コードページ」(つまり、文字セット)に分散されます。
  5. 各照合順序は、VARCHARデータに使用するコードページ(ある場合)を指定します(NVARCHARはすべての文字です)
  6. 文字列リテラルまたは変数をNVARCHAR(つまり、Unicode/UTF-16 /すべての文字)からVARCHAR(ほとんどの照合で指定されているコードページに基づく文字セット)に変換すると、デフォルトの照合データベースの
  7. 変換に使用されている照合順序のコードページに同じ文字が含まれていないが、「最適な」マッピングが含まれている場合、「最適な」マッピングが使用されます。
  8. 変換に使用されている照合順序のコードページに同じ文字が含まれていないか、「最適な」マッピングが含まれている場合、デフォルトの「置換」文字が使用されます(最も一般的には?)。

したがって、文字列リテラルにNVARCHARプレフィックスがないため、表示されているのはVARCHARからNへの変換です。また、データベースのデフォルトの照合順序のコードページにはまったく同じ文字が含まれていませんが、「最適な」マッピングが見つかりました。これが、2ではなく?を取得する理由です。 。

この効果は、次の簡単なテストを行うことで確認できます。

SELECT '₂', N'₂';

戻り値:

2    ₂

明確にするために、データベースのデフォルトの照合順序のコードページにまったく同じ文字が含まれている場合、そのコードページでは同じ文字に変換されます。そして、あなたの場合、NVARCHAR列に格納しているので、元のUnicode文字に再度変換されます。以下の最後の例は、この動作を示しています。

重要:文字列リテラルが解釈されるときに変換が行われることに注意してください。つまり、before列に格納されます。これは、列がその文字を保持できる場合でも、データベースのデフォルトの照合に基づいて、既に別の文字列に変換されていることを意味します。これは、すべてその文字列リテラルのN接頭辞を省略したためです。そして、これはまさにあなたが経験している(または経験していた)ものです。

たとえば、データベースのデフォルトの照合順序が韓国語の照合順序の1つ(4つの2バイト文字セットの1つ)である場合、その文字で「下付き文字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                           ₂                  ₂

ご覧のとおり、VARCHARデータに対してコードページ1252(Modern_Spanish照合が使用するのと同じコードページ)を使用するLatin1_General照合は、完全に一致していませんが、 "最適な」マッピング(これが表示されているものです)。ただし、VARCHARデータにコードページ949を使用する韓国語照合では、「下付き文字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₂

[〜#〜]更新[〜#〜]

ここで何が正確に行われているのか(つまり、すべての悲惨な詳細)に興味がある人は、私が投稿した2つの部分からなる調査を参照してください。

21
Solomon Rutzky