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


14

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

すなわち[Q]。[G] ---> [P]。[G]

編集:2017年3月20日:一部の人々は、ソーステーブルがターゲットテーブルの唯一のソースであるかどうかを尋ねました。はい、それが唯一のソースです。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?)は理想的なソリューションですが、他の方法も歓迎します。


2
まず、プロセスと、データの使用方法を検討します。のデータ[G]はにETLされ[P]ます。場合[G]varchar、かつETLプロセスはデータがになる唯一の方法である[P]プロセスが真のUnicode文字を追加しない限り、その後、任意のがあってはなりません。他のプロセスが追加または内のデータを変更した場合[P]、あなたはもっと注意する必要があります-現在のすべてのデータができるという理由だけでvarchar、その意味ではありませんnvarcharデータが明日を追加することができませんでした。同様に、データを消費しているものはどれでもデータを[P]必要とnvarcharする可能性があります。
RDFozz

回答:


10

列の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];

各列について1、その値のいずれかにunicodeが含まれているかどうかの結果を取得します。の結果0は、すべてのデータを安全に変換できることを意味します。

新しい列定義を使用してテーブルのコピーを作成し、そこにデータをコピーすることを強くお勧めします。適切な場所でそれを行うと、コストのかかる変換を行うため、コピーの作成がそれほど遅くなることはありません。コピーがあると、すべてのデータがまだ存在することを簡単に検証でき(EXCEPTキーワードを使用する方法の1つ)、操作を簡単に取り消すことができます。

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


@srutzkyからの回答と議論は非常によく有益な情報でしたが、Joeは私の質問が何を求めているのかを提供しました。したがって、私はジョーの答えを受け入れられた答えとしてマークしました。私も助けてくれた他の答えに投票しました。
ジョンG Hohengarten

@JohnGHohengartenとJoe:それでいい。この回答とスコットの回答に含まれていたため、クエリについては言及しませんでした。既にその型であるため、NVARCHAR列を変換する必要はないと言うだけNVARCHARです。また、変換不可能な文字をどのように判別したかはわかりませんが、列をVARBINARYに変換してUTF-16バイトシーケンスを取得できます。また、UTF-16は逆バイト順であるため、p= 0x7000を使用し、これら2バイトを逆にしてCode Pointを取得しますU+0070。ただし、ソースがVARCHARの場合、Unicode文字にすることはできません。他に何かが起こっています。さらに情報が必要です。
ソロモンラッツキー

@srutzkyキャストを追加して、データ型の優先順位の問題を回避しました。あなたはそれが必要でないことを正しいかもしれません。他の質問については、UNICODE()とSUBSTRING()を提案しました。そのアプローチは機能しますか?
ジョーオブビッシュ

@JohnGHohengarten and Joe:VARCHAR暗黙的にに変換されるので、データ型の優先順位は問題ではありませんが、NVARCHAR実行する方がよい場合がありますCONVERT(NVARCHAR(80), CONVERT(VARCHAR(80), column)) <> columnSUBSTRINGときどき機能しますが、で終わらない照合を使用する場合、補助文字で_SCは機能せず、ジョンが使用しているものでは機能しませんが、ここでは問題になりません。ただし、VARBINARYへの変換は常に機能します。とCONVERT(VARCHAR(10), CONVERT(NVARCHAR(10), '›'))にはなりません。?私はバイトを見たいと思うので、。ETLプロセスによって変換された可能性があります。
ソロモンラツキー

5

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

  1. このテーブルにデータを入力する以外に他のソースはあり[Q].[G]ますか?

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

  2. そこにある任意の近い将来にこのデータを移入するために追加のソースを追加することに関連する計画/議論は?

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

    これらの可能性の感覚を得るために周りに尋ねる必要があります。現在、この方向を指し示すものは何も言われていないと思います。それ以外の場合は、この質問をすることはないでしょうが、これらの質問が「いいえ」であると仮定されている場合、質問する必要があります。最も正確で完全な回答を得るための幅広い視聴者。

ここでの主な問題、(決して)変換できない Unicodeコードポイントがあることではなく、すべてが単一のコードページに収まらないコードポイントがあることです。これは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            ט ת             ? ?            ט ת             ט ת

ご覧のとおり、これらの文字すべて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上にあると言いましたが、どのエディションとは言いませんでした。エンタープライズエディションを使用している場合は、(スペースを節約するためだけに行っている可能性が高いので)この変換に関するすべてのことを忘れて、データ圧縮を有効にしてください。

Unicode圧縮の実装

Standard Editionを使用している場合(現在はareであると思われます)、もう1つの可能性があります。SP1にはすべてのエディションでデータ圧縮を使用する機能が含まれるため、SQL Server 2016にアップグレードします( 「😉」。

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

使用する照合を特定したら、次のいずれかを実行できます。

  • ALTER TABLE ... ALTER COLUMN ...するVARCHAR(現在の指定するには、必ずNULL/ NOT NULL時間のビットおよび87万行のトランザクション・ログ・スペースの多くを必要とし、設定)、OR

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


ソースとターゲットの両方で指定したCodePageクエリを実行しました。CodePageは1252で、collat​​ion_nameはソースとターゲットの両方でSQL_Latin1_General_CP1_CI_ASです。
ジョンG Hohengarten

@JohnGHohengarten一番下でまた更新しました。簡単にするために、使用している照合Latin1_General_100_CI_ASよりもはるかに優れている場合でも、同じ照合を維持できます。簡単な意味は、ソートと比較の振る舞いがそれらの間で同じであるということです。これは、先ほど述べた新しい照合順序ほど良くはありません。
ソロモンラッツキー

4

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

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

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

ナッツ

それでは、計算列を追加して、その数を把握しましょう。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行を返します

ナッツ

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

ナッツ

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

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

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

ナッツ

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

お役に立てれば!


1

「フィールドに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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.