web-dev-qa-db-ja.com

SQL Server一括挿入は一部のUnicode文字を正しく解釈し、他の文字は解釈しませんか?

何らかの理由で、MS SQL Server 2016一括挿入がUnicode文字を誤って解釈/変換します。

  • C9(É)から2B(+)
  • A1(¡)からED(í)
  • A0()からE1(á)
  • AE(®)からAB(")へ
  • CB(Ë)から2D(-)
  • D1(Ñ)から2D(-)
  • 92( ’)からC6(Æ)
  • 96(–)からFB(û)

つまり、Notepad ++とxxdはフラットファイルに0xC9があることを示していますが、一括挿入後にテーブルに "+"が表示され、SQL Serverでvarbinaryとしてキャストすると0x2Bと表示されます。バックアップにも0xC9があります。

25のフラットファイルをMS SQL Server 2016に一括挿入しています。これは15Gbのデータで、パイプ()フィールド区切り文字と[〜#〜] crlf [〜#を使用しています〜]行区切り文字。

提供されたバックアップの切り捨てられた構造に一括挿入します。バックアップと比較すると、違いがあります。注:データソースからのバックアップを25時間待つ必要がありますが、フラットファイルは15分で取得できます。

いくつかの違いは許容できますが(フラットファイルに適用している検索および置換)、多くはUnicode文字が誤って解釈されていることが原因です。

テーブルの例の構造は次のとおりです。

CREATE TABLE [dbo].[obfuscated_name](
    [ob_1] [int] NOT NULL,
    [ob_2] [int] NOT NULL,
    [ob_3] [int] NOT NULL,
    [ob_4] [nvarchar](300) NULL
) ON [PRIMARY]

データベース照合はデフォルトですSQL_Latin1_General_CP1_CI_AS。照合順序が異なる列はありません。この照合ではコードページ1252を使用する必要があります。これにより、問題のある文字が適切に解釈されます。

私のプロセスは常に流動的な本番データに対して実行されているため、他の変更がポップアップするのではないかと心配しています。問題を分離して手動で誤解を更新するのではなく、問題の原因を知りたいのです。

5
Dave Goldsmith

これはSQL Server(またはWindows)のバグではなく、ファイルを別のエンコーディング(つまり、「Unicode」)に変換する追加の手順が必要な状況でもありません。エンディアン」)。それは単なる誤解です。

コミュニケーションの崩壊の原因(常に同じですが、正しいです;-)は、単にソースデータの性質について同意していません。文字データをある場所から別の場所に移動するときは、両側にエンコードを指定することが重要です。はい、SQL_Latin1_General_CP1_*照合順序はコードページ1252を使用します。ただし、BULK INSERTまたはBCP.exeソースファイルのコードページが何であるかを指定しない場合、コードページがシステムのデフォルトであると想定します。

BULK INSERT の状態に関するドキュメント(CODEPAGE =引数の場合):

'OEM'(デフォルト)= charvarchar、またはtextの列のデータ型は、システムOEMコードページからSQL Serverに変換されますコードページ。

BCP.exe のドキュメントには、-Cスイッチの状態が記載されています。

OEM =クライアントが使用するデフォルトのコードページ。これは、-Cが指定されていない場合に使用されるデフォルトのコードページです。

また、Windowsのデフォルトのコードページは(少なくとも米国英語のシステムでは)437です。コマンドプロンプトで次のコマンドを実行すると、これを確認できます。

C:\> CHCP

戻ります:

Active code page: 437

ただし、ソースファイルはコードページ437を使用してエンコードされていません。コードページ1252を使用してエンコードされています。

だからここに何が起こっているのですか:

  1. バイトはバイトです。また、文字データを表すバイトは、エンコーディングを介してのみ解釈できます。ファイルを読み取るものはファイルから文字を読み取らず、ファイルのバイトを読み取り、指定されたエンコーディングに基づいて文字を表示します。
  2. BULK INSERT/BCPはバイトxC9を読み取ります。 xC9コードページ1252を使用すると、Éと表示されます。
  3. BULK INSERT/BCPにはソースコードページが指定されていないため、プロセスの現在のコードページがチェックされ、次のように通知されます。437
  4. BULK INSERT/BCPのバイトがxC9(コードページ用)437と表示されますが、BULK INSERT/BCPが表示していないため、これは表示されません)
  5. BULK INSERT/BCPは、コードページ1252を指定する照合を使用して、このデータを列に挿入します。
  6. SQL Serverは、着信データが宛先で使用しているものとは異なるコードページを使用していることを認識しているため、基になる値が変更された場合でも、文字が同じになるように(可能な限り)着信データを変換する必要があります。
  7. コードページ437からコードページ1252へのマッピングは、バイトxC9がバイトx2Bにマッピングされることを示しています。同様に、コードページ437(®として表示)のバイトxAE(コードページ1252では«)は、バイトxABにマップされます。コードページ1252(«としても表示されるため)。

次の例は、質問に記載されているすべての文字に対するこの変換を示しています。

DECLARE @CodePageConversion TABLE
(
   [ActualSource_CP1252] AS CONVERT(VARCHAR(10), CONVERT(BINARY(1),
                    [PerceivedSource_CP437])) COLLATE SQL_Latin1_General_CP1_CI_AS,

   [PerceivedSource_CP437] VARCHAR(10) COLLATE SQL_Latin1_General_CP437_CI_AS,

   [Source_Value] AS (CONVERT(BINARY(1), [PerceivedSource_CP437])),

   [Destination_CP1252] AS (CONVERT(VARCHAR(10), [PerceivedSource_CP437]
                  COLLATE SQL_Latin1_General_CP1_CI_AS)),

   [CP1252_Value] AS (CONVERT(BINARY(1), CONVERT(VARCHAR(10),
                  [PerceivedSource_CP437] COLLATE SQL_Latin1_General_CP1_CI_AS)))
);

INSERT INTO @CodePageConversion
VALUES      (0xC9), (0xA1), (0xA0), (0xAE), (0xCB), (0xD1), (0x92), (0x96);

SELECT * FROM @CodePageConversion;

これは次を返します:

ActualSource_CP1252  PerceivedSource_CP437  Source_Value  Destination_CP1252  CP1252_Value
É                    ╔                      0xC9          +                   0x2B
¡                    í                      0xA1          í                   0xED
                     á                      0xA0          á                   0xE1
®                    «                      0xAE          «                   0xAB
Ë                    ╦                      0xCB          -                   0x2D
Ñ                    ╤                      0xD1          -                   0x2D
’                    Æ                      0x92          Æ                   0xC6
–                    û                      0x96          û                   0xFB

0xC9、0XCB、および0xD1の文字はコードページ1252に存在しないため、「最適な」マッピングが使用されるため、変換後に+および-文字が使用されます。 。

また、宛先列がNVARCHARを使用している場合でも、これらのマッピングはすべて同じであるため、まったく同じ動作が見られます。

したがって、選択肢は次のとおりです。

  1. T-SQL BULK INSERTコマンドを使用する場合は、WITH CODEPAGE =オプションを次のいずれかの値で指定します。

    1. 'ACP'(これは'1252'と同じです)
    2. 'RAW'VARCHARに挿入する場合は列の照合順序のコードページを使用します。またはNVARCHARに挿入する場合は'OEM' /コードページ437と同じです)
    3. '1252'(これは'ACP'と同じです)
  2. または、BCP.exeを使用する場合、着信ファイルが-Cコマンドラインスイッチを介してコードページ1252を次のいずれかの値とともに使用することを示します(オプション#1の注記を参照) :

    1. ACP
    2. RAW
    3. 1252

その点に注意してください:

  1. 私はBULK INSERTでテストし、VARCHAR列に挿入し、質問に記載されている文字のセットとACP(これは[〜 #〜] a [〜#〜] NSI [〜#〜] c [〜#〜] ode [〜#〜] p [〜#〜] age)、RAW、および1252の値はすべて正しい結果を生成しました。
  2. WITH CODEPAGE =を指定しないと、O.P。が質問で報告したのと同じ結果が生成されました。これは、WITH CODEPAGE = 'OEM'を指定するのと同じでした。
  3. NVARCHAR列に挿入すると、ACP1252の両方が希望どおりに機能しましたが、RAWOEMと同じ結果を生成しました(つまり、列の照合順序で指定されるコードページ1252ではなくコードページ437)。
  4. BCP.exeを使用してテストしましたが、-Cスイッチを指定しないと、プロセスのコードページは使用されません。つまり、[〜#〜] chcp [〜#〜]を使用してコマンドを変更するプロンプトのコードページは効果がありませんでした。コードページ437がソースコードページとして引き続き使用されていました。

追伸ここのデータはすべて8ビットでエンコードされているため、使用されているUnicodeがないため、「Unicode文字」はありません。

7
Solomon Rutzky

これを修正するには、私は使用しなければなりませんでした:

BULK INSERT table FROM '\\path'
WITH (ERRORFILE = '\\error_path', FIELDTERMINATOR = 'term', DATAFILETYPE = 'widechar')

重要なポイントはDATAFILETYPE = 'widechar'

また、MS Notepadを使用してフラットファイルをユニコードタイプとして保存する必要がありました。

同じ問題があり、フラットファイルをユニコードに変換するためのより良いソリューションが必要な場合は、以下を調査してください https://stackoverflow.com/questions/623330/how-to-convert-txt-file-into-unicode

0
Dave Goldsmith