web-dev-qa-db-ja.com

SQL Serverは、最初の引数がNULLでなくても、COALESCE関数をすべて読み取りますか?

T-SQL COALESCE関数を使用していますが、最初の引数は実行された時間の約95%でnullになりません。最初の引数がNULLの場合、2番目の引数は非常に長いプロセスです。

_SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )
_

たとえば、_c.FirstName = 'John'_の場合でも、SQL Serverはサブクエリを実行しますか?

VB.NET IIF()関数を使用すると、2番目の引数がTrueの場合、コードは3番目の引数を読み取ります(使用されませんが)。

102
Curt

いいえ。ここに簡単なテストがあります:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

2番目の条件が評価されると、ゼロ除算の例外がスローされます。

MSDN Documentation に従って、これはCOALESCEがインタープリターによってどのように表示されるかに関連しています-CASEステートメントを記述する簡単な方法にすぎません。

CASEは、SQL Serverで(主に)確実に短絡する唯一の関数の1つであることがよく知られています。

ここの別の回答でAaron Bertrandによって示されているように、スカラー変数および集計と比較するときにいくつかの例外があります(これはCASECOALESCEの両方に適用されます):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

ゼロ除算エラーが生成されます。

これはバグと考える必要があり、原則としてCOALESCEは左から右に解析されます。

96
JNK

これはどうですか-だったItzik Ben-Ganによって私に報告された Jaime Lafargueによってそれについて伝えられました

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

結果:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

もちろん、ささいな回避策がありますが、重要なのは、CASE常に左から右への評価/短絡を保証しないことです。 ここでバグを報告しました で、「仕様による」としてクローズされました。その後、ポールホワイトは この接続項目 を提出し、修正済みとしてクローズされました。それ自体は修正されたためではなく、集計がCASE式の評価順序を変更できるシナリオのより正確な説明でBooks Onlineを更新したためです。私は最近 これについてここにもっとブログを書いた

[〜#〜] edit [〜#〜]単なる補遺ですが、これらはエッジのケースであることに同意しますほとんどの場合左から右への評価と短絡に頼ることができ、これらはドキュメントと矛盾するバグであり、おそらく最終的には修正されます(これは明確ではありません-以下を参照してください- Bart Duncanのブログ投稿 での会話をアップする理由を確認するには)、それを否定する単一のEdgeケースがあっても常に何かが真実であると人々が言うとき、私は同意する必要があります。 Itzikや他の人がこのような孤独なバグを見つけることができれば、少なくとも他のバグも存在する可能性があるということになります。そして、OPのクエリの残りの部分がわからないので、彼がこの短絡に依存するが、結局それに噛み付かれるとは断言できません。だから私にとって、より安全な答えは:

ドキュメントに記載されているように、通常CASEを使用して左から右および短絡を評価できますが、次のように言うのは正確ではありません常にそうすることができます。このページには2つのケースが示されていますが、これは正しくありません。また、SQL Serverの公開されているバージョンではどちらのバグも修正されていません。

[〜#〜] edit [〜#〜]これは別のケースです (私はそれをやめる必要があります) CASE式は、集計が含まれていなくても、期待する順序で評価されません。

75
Aaron Bertrand

ドキュメントは、意図CASEを短絡させるためのものであることを合理的に明らかにしています。 Aaronが言及 のように、これが常に正しいとは限らないことが示されている事例がいくつか報告されています。これまでのところ、これらのほとんどはバグとして認識され、修正されています。

CASE(したがってCOALESCE)には、副作用のある関数またはサブクエリが使用される他の問題があります。考慮してください:

SELECT COALESCE((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);

Hugo Kornelisによる バグレポート で説明されているように、COALESCEフォームはしばしばnullを返します。

オプティマイザ変換と共通式追跡で実証された問題は、CASEがすべての状況で短絡することを保証することが不可能であることを意味します。

CASEが一般的に短絡することにはある程度の自信があると思います(特に、適度に熟練した人が実行プランを検査し、その実行プランがプランガイドまたはヒントで「強制」されている場合)。絶対的な保証が必要です。式をまったく含まないSQLを作成する必要があります。

38
Paul White 9

CASE/COALESCEが短絡しない別のケースに遭遇しました。次のTVFを渡すと、PK違反が発生します1パラメータとして。

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

次のように呼び出された場合

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

または

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

どちらも結果を出します

PRIMARY KEY制約「PK__F__3BD019A800551192」の違反。オブジェクト 'dbo。@ T'に重複するキーを挿入することはできません。重複するキーの値は(1)です。

SELECT(または少なくともテーブル変数の母集団)がまだ実行されており、ステートメントの分岐に到達してはならない場合でもエラーが発生することを示しています。 COALESCEバージョンの計画は以下のとおりです。

Plan

このクエリの書き換えにより、問題が回避されたようです

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

どちらが計画を与える

Plan2

20
Martin Smith

もう一つの例

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

クエリ

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

T2に対する読み取りをまったく表示しません。

T2のシークはパススルー述語の下にあり、演算子は実行されません。だが

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

DoesT2が読み取られたことを示します。 T2の値は実際には必要ありませんが。

もちろん、これは驚くべきことではありませんが、セットベースの宣言型言語で短絡が何を意味するかという問題が発生する場合にのみ、カウンターサンプルリポジトリに追加する価値があると思いました。

9
Martin Smith

私はあなたが考慮しなかったかもしれない戦略に言及したかっただけです。ここではマッチしないかもしれませんが、時には重宝します。この変更により、パフォーマンスが向上するかどうかを確認します。

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

これを行う別の方法はこれです(基本的には同等ですが、必要に応じて他のクエリからより多くの列にアクセスできます):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

基本的に、これはテーブルを「ハード」に結合する手法ですが、行を結合する条件を含めます。私の経験では、これは実行計画に非常に役立ちました。

7
ErikE

実際の標準では、式のデータ型全体を決定するには、すべてのWHEN句(およびELSE句)を解析する必要があるとされています。エラーがどのように処理されるかを判断するには、古いメモをいくつか取り出さなければなりません。しかし、手に負えないほど、1/0は整数を使用するので、それはエラーであると思います。整数データ型のエラーです。合体リストにnullしかない場合は、データ型を判別するのが少し難しく、それが別の問題です。

3
Joe Celko

いいえ、そうではありません。 c.FirstNameNULLの場合にのみ実行されます。

ただし、自分で試してみてください。実験。あなたのサブクエリは長いと言っていました。基準。これについてあなた自身の結論を導き出してください。

実行中のサブクエリに対する@Aaronの回答はより完全です。

ただし、まだクエリをやり直してLEFT JOINを使用する必要があると思います。ほとんどの場合、サブクエリは、LEFT JOINsを使用するようにクエリを修正することで削除できます。

サブクエリを使用する際の問題は、メインクエリの結果セットの各行に対してサブクエリが実行されるため、ステートメント全体の実行が遅くなることです。

2