web-dev-qa-db-ja.com

NVARCHAR列の値が実際にUnicodeであるかどうかを検出する

一部のSQL Serverデータベースを継承しました。 SQL Server 2014 Standardのソースデータベース(「Q」と呼びます)には、ETLを取得するテーブルが1つあり(「G」と呼びます)、約8670万行と41列の幅があります。 SQL Server 2008 R2 Standardで同じテーブル名を持つターゲットデータベース(「P」と呼びます)。

つまり、[Q]。[G] ---> [P]。[G]

編集:3/20/2017:ソーステーブルがターゲットテーブルへの唯一のソースであるかどうかを尋ねる人がいます。はい、それが唯一の情報源です。 ETLに関する限り、実際の変換は行われていません。事実上、ソースデータの1:1コピーを意図しています。したがって、このターゲットテーブルにソースを追加する計画はありません。

[Q]。[G]の列の半分以上がVARCHAR(ソーステーブル)です。

  • 13列はVARCHAR(80)です。
  • 列の9つはVARCHAR(30)です。
  • 列の2つはVARCHAR(8)です。

同様に、[P]。[G]の同じ列はNVARCHAR(ターゲットテーブル)であり、同じ幅の同じ列数を持ちます。 (つまり、長さは同じですが、NVARCHAR)。

  • 13列はNVARCHAR(80)です。
  • 列の9つはNVARCHAR(30)です。
  • 列の2つはNVARCHAR(8)です。

これは私のデザインではありません。

ALTER [P]。[G](ターゲット)列のデータ型をNVARCHARからVARCHARに変更したい。安全に(変換によるデータ損失なしに)実行したい。

ターゲットテーブルの各NVARCHAR列のデータ値を確認して、列に実際にUnicodeデータが含まれているかどうかを確認するにはどうすればよいですか?

各NVARCHAR列の各値(ループ内?)をチェックして、値のいずれかが本物のUnicodeかどうかを確認できるクエリ(DMV?)が理想的なソリューションですが、他の方法も歓迎します。

14

列の1つにUnicodeデータが含まれていないとします。すべての行の列値を読み取る必要があることを確認するには。列にインデックスがない限り、行ストアテーブルでは、テーブルからすべてのデータページを読み取る必要があります。そのことを念頭に置いて、すべての列チェックを組み合わせて、テーブルに対する単一のクエリにするのは非常に理にかなっていると思います。そうすれば、テーブルのデータを何度も読み取ることがなくなり、カーソルやその他の種類のループをコーディングする必要がなくなります。

単一の列をチェックするには、これを行うだけでよいと考えます。

SELECT COLUMN_1
FROM [P].[Q]
WHERE CAST(COLUMN_1 AS VARCHAR(80)) <> CAST(COLUMN_1 AS NVARCHAR(80));

NVARCHARからVARCHARへのキャストでも、Unicode文字がある場合を除いて、同じ結果が得られます。 Unicode文字は?に変換されます。したがって、上記のコードはNULLのケースを正しく処理する必要があります。チェックする列は24個あるので、スカラー集計を使用して単一のクエリで各列をチェックします。 1つの実装は以下のとおりです。

SELECT 
  MAX(CASE WHEN CAST(COLUMN_1 AS VARCHAR(80)) <> CAST(COLUMN_1 AS NVARCHAR(80)) THEN 1 ELSE 0 END) COLUMN_1_RESULT
...
, MAX(CASE WHEN CAST(COLUMN_14 AS VARCHAR(30)) <> CAST(COLUMN_14 AS NVARCHAR(30)) THEN 1 ELSE 0 END) COLUMN_14_RESULT
...
, MAX(CASE WHEN CAST(COLUMN_23 AS VARCHAR(8)) <> CAST(COLUMN_23 AS NVARCHAR(8)) THEN 1 ELSE 0 END) COLUMN_23_RESULT
FROM [P].[Q];

列ごとに、その値のいずれかにUnicodeが含まれている場合は、1の結果が返されます。 0の結果は、すべてのデータを安全に変換できることを意味します。

新しい列定義でテーブルのコピーを作成し、そこにデータをコピーすることを強くお勧めします。適切な場所で実行すると、高額な変換が行われるため、コピーの作成がそれほど遅くなることはありません。コピーがあると、すべてのデータがまだ存在することを簡単に検証でき( [〜#〜] except [〜#〜] キーワードを使用する方法の1つ)、操作を元に戻すことができますとても簡単に。

また、現在Unicodeデータがない可能性があることに注意してください。将来のETLが以前にクリーンな列にUnicodeをロードする可能性があります。 ETLプロセスでこれに対するチェックがない場合は、この変換を行う前にチェックを追加することを検討する必要があります。

10
Joe Obbish

何かを行う前に、質問に対するコメントで@RDFozzによって提起された質問を検討してください。

  1. このテーブルに_[Q].[G]_以外のソースanyがありますか?

    応答が「これがonlyこの宛先テーブルのデータのソースであることを100%確信している」以外の場合、現在データがあるかどうかに関係なく、変更を加えないでください。テーブル内のデータを失うことなく変換できます。

  2. 近い将来、このデータを追加するための追加のソースの追加に関連するany計画/ディスカッションはありますか?

    そして私は関連する質問を追加します:itNVARCHARに変換することによって現在のソーステーブル(つまり_[Q].[G]_)で複数の言語をサポートすることについて議論はありましたか?

    あなたはこれらの可能性の感覚を得るために周りを尋ねる必要があります。私はあなたが現在この方向を指すようなことを何も言われていないと思います、そうでなければあなたはこの質問をするつもりはありませんが、これらの質問が「いいえ」であると仮定されているなら、彼らは尋ねられる必要があります。最も正確で完全な回答を得るための十分な聴衆。

ここでの主な問題は、Unicodeコードポイントができない変換することではなく、すべてが1つのコードページに収まらないコードポイントがあることです。これがUnicodeのいいところです。すべてのコードページの文字を保持できます。 NVARCHAR(コードページを気にする必要がない場所)からVARCHARに変換する場合は、宛先列の照合で同じコードが使用されていることを確認する必要がありますソース列としてのページ。これは、1つのソース、または同じコードページを使用する複数のソースのいずれか(ただし、必ずしも同じ照合順序である必要はない)を想定しています。ただし、複数のコードページを持つ複数のソースがある場合、次の問題が発生する可能性があります。

_DECLARE @Reporting TABLE
(
  ID INT IDENTITY(1, 1) PRIMARY KEY,
  SourceSlovak VARCHAR(50) COLLATE Slovak_CI_AS,
  SourceHebrew VARCHAR(50) COLLATE Hebrew_CI_AS,
  Destination NVARCHAR(50) COLLATE Latin1_General_CI_AS,
  DestinationS VARCHAR(50) COLLATE Slovak_CI_AS,
  DestinationH VARCHAR(50) COLLATE Hebrew_CI_AS
);

INSERT INTO @Reporting ([SourceSlovak]) VALUES (0xDE20FA);
INSERT INTO @Reporting ([SourceHebrew]) VALUES (0xE820FA);

UPDATE @Reporting
SET    [Destination] = [SourceSlovak]
WHERE  [SourceSlovak] IS NOT NULL;

UPDATE @Reporting
SET    [Destination] = [SourceHebrew]
WHERE  [SourceHebrew] IS NOT NULL;

SELECT * FROM @Reporting;

UPDATE @Reporting
SET    [DestinationS] = [Destination],
       [DestinationH] = [Destination]

SELECT * FROM @Reporting;
_

戻り値(2番目の結果セット):

_ID    SourceSlovak    SourceHebrew    Destination    DestinationS    DestinationH
1     Ţ ú             NULL            Ţ ú            Ţ ú             ? ?
2     NULL            ט ת             ? ?            ט ת             ט ת
_

ご覧のとおり、これらのすべての文字canは、同じVARCHAR列ではなく、VARCHARに変換されます。

次のクエリを使用して、ソーステーブルの各列のコードページを確認します。

_SELECT OBJECT_NAME(sc.[object_id]) AS [TableName],
       COLLATIONPROPERTY(sc.[collation_name], 'CodePage') AS [CodePage],
       sc.*
FROM   sys.columns sc
WHERE  OBJECT_NAME(sc.[object_id]) = N'source_table_name';
_

それは言った....

SQL Server 2008 R2を使用しているとおっしゃっていましたが、エディションは何も言っていませんでした。 Enterprise Editionを使用している場合は、この変換作業をすべて忘れて(スペースを節約するためにそうしている可能性があるため)、データ圧縮を有効にします。

nicode圧縮の実装

Standard Editionを使用している場合(そして、あなたが????であるように思われる場合)、別の問題が発生する可能性があります。SQLServer 2016へのアップグレードは、SP1にすべてのエディションでデータ圧縮を使用する機能が含まれているためです。ロングショット" ???? )。

もちろん、データのソースが1つしかないことが明確になったため、ソースにUnicodeのみの文字や特定のコード外の文字を含めることができなかったので、心配する必要はありません。ページ。その場合、注意が必要なのは、ソース列と同じ照合順序を使用するか、同じコードページを使用する少なくとも1つを使用することだけです。つまり、ソース列が_SQL_Latin1_General_CP1_CI_AS_を使用している場合、宛先で_Latin1_General_100_CI_AS_を使用できます。

使用する照合がわかったら、次のいずれかを実行できます。

  • _ALTER TABLE ... ALTER COLUMN ..._をVARCHARにする(必ず現在のNULL/_NOT NULL_設定を指定してください)。これには、87の時間と大量のトランザクションログ領域が必要です。 100万行、または

  • それぞれに対して新しい「ColumnName_tmp」列を作成し、UPDATEを実行してTOP (1000) ... WHERE new_column IS NULLを実行してゆっくりとデータを入力します。すべての行にデータが入力されたら(そしてそれらがすべて正しくコピーされたことを検証します!UPDATEがある場合は、UPDATEを処理するためのトリガーが必要になる場合があります)、明示的なトランザクションで、_sp_rename_を使用して「現在の"列を" _Old "にしてから、新しい" _tmp "列を指定して、名前から" _tmp "を削除します。次に、テーブルで_sp_reconfigure_を呼び出して、テーブルを参照するキャッシュされたプランを無効にします。テーブルを参照するビューがある場合は、_sp_refreshview_(またはそのようなもの)を呼び出す必要があります。アプリを検証し、ETLがアプリで正しく動作したら、列を削除できます。

5
Solomon Rutzky

私は実際の仕事をしていたときから、これについていくらか経験があります。当時はベースデータを保持したいと考えていたので、シャッフルで失われる可能性のある文字が含まれる可能性のある新しいデータも考慮する必要があったため、非永続計算列を使用しました。

SOデータダンプ からのスーパーユーザーデータベースのコピーを使用した簡単な例を次に示します。

Unicode文字のDisplayNameがあることがすぐにわかります。

Nuts

それでは、計算された列を追加して、その数を調べましょう! DisplayName列はNVARCHAR(40)です。

USE SUPERUSER

ALTER TABLE dbo.Users
ADD DisplayNameStandard AS CONVERT(VARCHAR(40), DisplayName)

SELECT COUNT_BIG(*)
FROM dbo.Users AS u
WHERE u.DisplayName <> u.DisplayNameStandard

カウントは約3000行を返します

Nuts

ただし、実行計画は少し厄介です。クエリは高速に終了しますが、このデータセットはそれほど大きくありません。

Nuts

計算列は、インデックスを追加するために永続化する必要がないため、次のいずれかを実行できます。

CREATE UNIQUE NONCLUSTERED INDEX ix_helper
ON dbo.Users(DisplayName, DisplayNameStandard, Id)

これにより、少し整然とした計画が得られます。

Nuts

これがtheの答えではない場合、それはアーキテクチャの変更を伴うため理解しますが、データのサイズを考慮すると、おそらくインデックスの追加を検討していますとにかくテーブルを自己結合するクエリに対処します。

お役に立てれば!

4
Erik Darling

フィールドにUnicodeデータが含まれているかどうかを確認する方法 の例を使用すると、各列のデータを読み取ってCASTを実行し、以下を確認できます。

--Test 1:
DECLARE @text NVARCHAR(100)
SET @text = N'This is non-Unicode text, in Unicode'
IF CAST(@text AS VARCHAR(MAX)) <> @text
PRINT 'Contains Unicode characters'
ELSE
PRINT 'No Unicode characters'
GO

--Test 2:
DECLARE @text NVARCHAR(100)
SET @text = N'This is Unicode (字) text, in Unicode'
IF CAST(@text AS VARCHAR(MAX)) <> @text
PRINT 'Contains Unicode characters'
ELSE
PRINT 'No Unicode characters'

GO
1
Scott Hodgin