NULL以外の値またはNULLの列をチェックするSQLクエリを記述する最適な方法


17

デフォルト値としてNULLを持つパラメーターを持つSPがあり、次のようなクエリを実行したい:

SELECT ...
FROM ...
WHERE a.Blah = @Blah AND (a.VersionId = @VersionId OR (@VersionId IS NULL AND a.VersionId IS NULL));

WHERE非NULL値とのNULL値の両方のための上記のチェック@VersionId

代わりにIFステートメントを使用し、クエリをNULL以外を検索するクエリとNULLを検索するクエリに複製する方がパフォーマンスの点で優れているでしょうか?:

IF @VersionId IS NULL BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId IS NULL;
ELSE BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId = @VersionId;
END

または、クエリオプティマイザーは本質的に同じにしますか?

更新:

(注:SQL Serverを使用しています)

(そして、私が知る限りa.VersionId = @VersionId、両方のケースに使用してもうまくいきませんか?)



私は通常、次を使用します:ISNULL(a.VersionId、@VersionId)= @VersionId
628426

回答:


36

このパターン

column = @argument OR (@argument IS NULL AND column IS NULL)

に置き換えることができます

EXISTS (SELECT column INTERSECT SELECT @argument)

これにより、NULLとNULLが一致し、エンジンがインデックスをcolumn効率的に使用できるようになります。この手法の詳細な分析については、Paul Whiteのブログ記事を参照してください。

特定のケースには2つの引数があるので、同じマッチング手法を使用できます。@Blahその方法で、WHERE句全体を多少簡潔に書き換えることができます。

WHERE
  EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)

これはのインデックスで高速に動作し(a.Blah, a.VersionId)ます。


または、クエリオプティマイザーは本質的に同じにしますか?

この場合、はい。(少なくとも)SQL Server 2005以降のすべてのバージョンで、オプティマイザーはパターンcol = @var OR (@var IS NULL AND col IS NULL)を認識し、適切なIS比較に置き換えることができます。これは内部書き換え一致に依存しているため、これが常に信頼できるとは限らない、より複雑な場合があります。

2008 SP1 CU5以降のバージョンのSQL Server では、を介してパラメーター埋め込み最適化を使用するオプションもありますOPTION (RECOMPILE)。ここで、パラメーターまたは変数のランタイム値は、コンパイル前にリテラルとしてクエリに埋め込まれます。

したがって、少なくとも大部分は、この場合の選択はスタイルの問題ですが、INTERSECT構造は間違いなくコンパクトでエレガントです。

次の例は、各バリエーションの「同じ」実行計画を示しています(リテラルと変数参照は除外)。

DECLARE @T AS table
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL

    UNIQUE CLUSTERED (c1, c2)
);

-- Some data
INSERT @T
    (c1, c2, c3)
SELECT 1, 1, 1 UNION ALL
SELECT 2, 2, 2 UNION ALL
SELECT NULL, NULL, NULL UNION ALL
SELECT 3, 3, 3;

-- Filtering conditions
DECLARE 
    @c1 integer,
    @c2 integer;

SELECT
    @c1 = NULL,
    @c2 = NULL;

-- Writing the NULL-handling out explicitly
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
);

-- Using INTERSECT
SELECT * 
FROM @T AS T
WHERE EXISTS 
(
    SELECT T.c1, T.c2 
    INTERSECT 
    SELECT @c1, @c2
);

-- Using separate queries
IF @c1 IS NULL AND @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 IS NULL
ELSE IF @c1 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 = @c2
ELSE IF @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 IS NULL
ELSE
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 = @c2;

-- Using OPTION (RECOMPILE)
-- Requires 2008 SP1 CU5 or later
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
)
OPTION (RECOMPILE);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.