web-dev-qa-db-ja.com

nvarchar(10)をchar(10)にコピーする際の「文字列またはバイナリデータは切り捨てられます」エラー

1つのSQLテーブル/列から別のSQLテーブルに値を挿入しています。他の理由によりこれらの列のデータ型は異なりますが、SQL Server 2014でnvarchar(10)のソースとchar(10)の宛先が原因でエラーが発生することがある理由がわかりません。

文字列型やバイナリは省略されます。

len(sourcecol)= 10およびdatalength(sourcecol)= 20。

Nvarcharタイプのソース列に格納されているいくつかの非表示のスペース/文字が原因である可能性がありますか?

3
user5664489

OPからのデータとテーブルDDLの例がないと、このエラーの正確な原因がOPのであるかどうかを特定することは困難ですただし、これは以下の理由により、他の人に動作(したがって問題)が発生する可能性があります。

一部のコードページ(各CHAR/VARCHARフィールドの照合順序によって決定される)では、1バイトに収まる256文字を超えるマップを可能にするために、2バイト文字を使用できます。文字列フィールドの最大長は実際には文字ではなくバイトの問題であるため、最大長が10のCHAR/VARCHARフィールドは10バイトしか保持できず、どこからでも保持できます。 5-10文字。ソースフィールドがNVARCHAR(10)の場合、これらの10文字は、VARCHARフィールドで1バイトを超える文字にマップできます。このエラーが発生するのに必要なのは、9つの「通常の」シングルバイト文字と11の実際のバイトを必要とする1つのダブルバイト文字だけです。

したがって、宛先テーブルのCHAR(10)フィールドの照合順序を確認してください。そのフィールドの照合順序の名前がわかったら、それがどのコードページが使用されているかを示し、932、936、949、または950のいずれかである場合、これが問題の原因である可能性が非常に高くなります。この場合、宛先フィールドを変更して最大長を20にします(10文字すべてが2バイトの場合に安全を確保するため)。私はVARCHAR(20)をお勧めしますが、本当に空白埋めが本当に好きなら、CHAR(20)を実行してください。

少しより詳細な説明が続きます:


VARCHAR(つまり8ビット拡張ASCII)データはシングルバイトであり、NVARCHAR(つまりUnicode)データはダブルバイトであることが一般的に理解されています。文字列データ(およびエンコーディング)のこの理解は、米国英語のアルファベット(および他のかなりの数の、少なくともほとんどの「アクティブ」な言語)を扱うときに常に当てはまります。そして、ほとんどの言語で作業しているときはそうですが、それは常に正しいとは限りません。

この理解がUnicodeデータに当てはまらない方法は、この質問の範囲を超えているため、ここでは詳しく説明しません。

しかし、私たちの旧友であるVARCHARに関しては、文字が1バイトではなく2バイトを占めることを許容する状況がいくつかあります。はい、あなたはそれを正しく読みました。しかし、どうやって?まあ、Unicodeが登場する前は、255文字を超えるアルファベットを含む一部の文化では、ネイティブアルファベットを使用したいと考えていました。シングルバイト(256の値の範囲)内ではそれができないため、2バイト文字セット(DBCS)が考案されました。これらは異なるコードページとして処理され、WindowsとSQL Serverはそのうちの4つをサポートします。

  • 932 =日本語(Microsoft内ではShift-JIS、Microsoft以外ではWindows-31J)
  • 936 =簡体字中国語(GB2312)
  • 949 =韓国語
  • 950 =繁体字中国語(Big5)

「2バイト」という用語がNVARCHARのようなものであり、16ビットシーケンスでのみ機能するという意味と混同しないでください。これらのコードページは、実際にはUTF-8に似た可変長エンコーディングであり、少なくとも最初の128の値(0〜127)と(128〜255の範囲)の一部に1バイト(8ビット)を使用します。

これは切り捨てエラーにどのように適合しますか?まあ、NVARCHARVARCHARの両方のデータ型の最大長は、文字ではなくバイトで表現されています。つまり、VARCHAR(10)は、10バイト未満の文字が10バイトに収まる場合でも、最大10バイトです。同様に、NVARCHAR(10)は、20バイトに収まる文字が10文字未満であっても、最大20バイトです。

これを念頭に置いて、Unicodeからコードページに変換すると、同じ文字へのマッピングが試行されることがわかります。ほとんどのコードページでは、これらの文字のサイズはすべて1バイトです。しかし、4つのDBCSコードページには、存在する(したがってマップできる)かなりの数の文字があり、2バイトです(そうでない場合は存在しません)。

ここでの問題は、DBCSコードページが(宛先に対して)使用されており、マップされている文字の少なくとも1つがVARCHARタイプ内で2バイトを使用していることです。

以下は、この動作の実際の例です。

テスト設定

まず、このコードを実行してテストを設定します。このテストを実行するデータベースのデフォルトの照合順序は重要ではありません。ここでは、メインのUnicode文字セットの最初の65,536個のコードポイントのそれぞれを保持する一時テーブルを作成しています(最初の65,536を超える文字には2つのコードポイントが必要で、それぞれ4バイトですが、ここでも範囲外です。現在の問題に関連する動作を変更します;)

SET NOCOUNT ON;

IF (OBJECT_ID(N'tempdb..#Source') IS NOT NULL)
BEGIN
  DROP TABLE #Source;
END;

CREATE TABLE #Source ([Character] NVARCHAR(1));

;WITH nums (num) AS
(
  SELECT TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
  FROM   [master].[sys].[columns] sc1
  CROSS JOIN [master].[sys].[columns] sc2
)
INSERT INTO #Source ([Character])
  SELECT NCHAR(num - 1)
  FROM nums;

SELECT * FROM #Source;

テスト1:Latin1文字セット(コードページ1252)

以下を実行してもエラーは発生しません。うまくいきます。ただし、最初の256個の値のみが変換されます。これは、任意の1バイト文字セット(SBCS)に適合するすべての値であるためです。

IF (OBJECT_ID(N'tempdb..#Destination_CP1252') IS NOT NULL)
BEGIN
  DROP TABLE #Destination_CP1252;
END;

CREATE TABLE #Destination_CP1252 ([Character] VARCHAR(1) COLLATE Latin1_General_100_CI_AS);

INSERT INTO #Destination_CP1252 ([Character])
  SELECT [Character]
  FROM   #Source;

SELECT [Character],
       LEN([Character]) AS [NumberOfCharacters],
       DATALENGTH([Character]) AS [NumberOfBytes],
       CONVERT(VARBINARY(2), [Character]) AS [BinaryValue]
FROM   #Destination_CP1252;

テスト2:日本語(Shift-JIS)文字セット(コードページ932)

IF (OBJECT_ID(N'tempdb..#Destination_CP932') IS NOT NULL)
BEGIN
  DROP TABLE #Destination_CP932;
END;

CREATE TABLE #Destination_CP932 ([Character] VARCHAR(1) COLLATE Japanese_Unicode_CI_AS);

INSERT INTO #Destination_CP932 ([Character])
  SELECT [Character]
  FROM   #Source;

上記のコードを実行すると、次のエラーが発生します。

メッセージ8152、レベル16、状態2、行1
文字列型やバイナリは省略されます。
ステートメントは終了されました。

これが2バイト文字セット(DBCS)変換の問題である場合は、フィールドサイズを1バイト増やすと修正されます。最初に、テーブルに実際に何も挿入されていないことを確認します(次のINSERTステートメントがデータを入れるものであることを確実にするため)。

SELECT *
FROM   #Destination_CP932;
-- no rows

すごい。次を実行します。

ALTER TABLE #Destination_CP932
  ALTER COLUMN [Character] VARCHAR(2) COLLATE Japanese_Unicode_CI_AS;

INSERT INTO #Destination_CP932 ([Character])
  SELECT [Character]
  FROM   #Source;

エラーはありません。ウフー!変換された文字を見てみましょう:

SELECT [Character],
       LEN([Character]) AS [NumberOfCharacters],
       DATALENGTH([Character]) AS [NumberOfBytes],
       CONVERT(VARBINARY(2), [Character]) AS [BinaryValue]
FROM   #Destination_CP932
WHERE  1 = 1
--AND    DATALENGTH([Character]) > 1
--AND    [Character] <> '?'

結果をスクロールする場合は、[NumberOfBytes]および[BinaryValue]フィールドに注意する必要があります。

2バイト値だけを表示するには、次の行のコメントを外して、再実行します。

AND    DATALENGTH([Character]) > 1

コードページ932の値の全範囲を表示するには、DATALENGTH where条件を再度コメント化し、次の行のコメントを外して再実行します。

AND    [Character] <> '?'

カウントに9483文字が表示されます。公平を期すために、除外された実際の?文字を考慮して1を追加すると、VARCHARフィールド????で表される合計9484文字の付与が得られます。

11
Solomon Rutzky