社内データウェアハウスの拡張に取り組んでいるコンサルタントがいます。私はコードレビューを行っていて、すべてのロードプロシージャでこのパターンに遭遇しました。
MERGE [EDHub].[Customer].[Class] AS TARGET
USING (
SELECT <columns>
FROM [dbo].[vw_CustomerClass]
WHERE JHAPostingDate = @PostingDate
) AS SOURCE
ON TARGET.BankId = SOURCE.BankId -- This join is on the business keys
AND TARGET.Code = SOURCE.Code
WHEN NOT MATCHED BY TARGET
THEN
<INSERT Statement>
WHEN MATCHED
AND TARGET.IsLatest = 1
AND EXISTS (
SELECT SOURCE.[HASH]
EXCEPT
SELECT TARGET.[Hash]
)
THEN
<UPDATE Statement>
Gistは、新しいビジネスキーがある場合に挿入しますが、ビジネスキーが存在し、属性のハッシュが現在の行と一致しない場合は、古い行を更新して新しい行を挿入します(コードの後半)。すべて正常に動作しますが、このコードに到達したときに一時停止しました
AND EXISTS (
SELECT SOURCE.[HASH]
EXCEPT
SELECT TARGET.[Hash]
)
SOURCE。[HASH] <> TARGET。[Hash]に比べて複雑すぎるようです。 EXCEPTは正確なNULL比較を行いますが、私たちの場合、ハッシュがNULLになることはありません(またはより大きな問題があります)。コードを読みやすくして、誰かがコードを保守しなければならないときに混乱しないようにしたいと思います。私はそれについて私たちのコンサルタントに尋ねました、そして彼らは集合演算のためにそれはより速いかもしれないと推測しました、しかし私は簡単なテスト(以下のテストコード)を書くことにしました。
最初に気付いたのは、EXISTS/EXCEPTのクエリプランがより複雑だったということですが、それは必ずしも悪いことではありません
私は選択した各クライアント統計を実行し、<>結合はEXISTS/EXCEPTを使用した場合、合計実行時間を12,000対25,000にした。これをコンサルタントに伝えて、そのステートメントをリファクタリングしたいのですが、ここでフィードバックを得たいと思っています。
テストスクリプト:
CREATE TABLE r (hash VARBINARY(8000))
CREATE TABLE l (hash VARBINARY(8000))
SET NOCOUNT ON
DECLARE @x INT = 10000
WHILE @x <> 0 BEGIN
INSERT INTO dbo.r ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))
INSERT INTO dbo.l ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))
SET @x = @x-1
END
INSERT INTO dbo.r ( hash ) VALUES ( NULL )
INSERT INTO dbo.l ( hash ) VALUES ( NULL )
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE ISNULL(r.hash,0) <> ISNULL(l.hash,0)
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE EXISTS(SELECT r.hash except select l.HASH)
センチネル値を使用するISNULL
は好きではありません。現在またはその後ずっとデータに正当に表示されない値を選択する必要があり、個人的にこれらを含む式を推論するのがより困難であることがわかります。
テストリグについて、クエリを表現する4つの異なる方法を試して、指定された結果を得ました。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE r.hash <> l.hash
OR ( r.hash IS NULL
AND l.hash IS NOT NULL )
OR ( l.hash IS NULL
AND r.hash IS NOT NULL )
SQL Server実行時間:CPU時間= 30968ミリ秒、経過時間= 8230ミリ秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE ISNULL(r.hash, 0) <> ISNULL(l.hash, 0)
SQL Server実行時間:CPU時間= 31594ミリ秒、経過時間= 9230ミリ秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE EXISTS(SELECT r.hash
EXCEPT
SELECT l.HASH)
SQL Server実行時間:CPU時間= 46531ミリ秒、経過時間= 13191ミリ秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE NOT EXISTS(SELECT r.hash
INTERSECT
SELECT l.HASH)
SQL Server実行時間:CPU時間= 23812ミリ秒、経過時間= 6760ミリ秒。
したがって、それを基にして、最後のパターンは、パターンに不慣れな人のための ドキュメント化されていないクエリプラン:等価比較 へのリンクを含むコードコメントとともに、明らかに勝者になります。
ただし、このパターンが実際のMERGE
クエリでも再現可能かどうかをテストする必要があります。
私はあなたの調査結果に同意します。コンサルタントが作成したEXCEPT
パターンはNULL値を処理し、非常に多数の比較列を管理するための非常に優れた方法でもあります。
しかし、列をNULLにすることができず、パフォーマンスが低下する場合は、それを保持する意味がないと思います。
あなたのテストcould以下の点で不正確です:
このパターンは、BIMLパッケージまたは同様のETLツールによって生成される列比較の一般的なソリューションである可能性があります。