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


98

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番目の引数を読み取ります(使用されなくても)。

回答:


95

いや。以下に簡単なテストを示します。

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

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

MSDNドキュメントによると、これはCOALESCEインタープリターによる表示方法に関連していますCASE。ステートメントを記述する簡単な方法です。

CASE (ほとんど)確実に短絡するSQL Serverの唯一の機能の1つであることがよく知られています。

アーロン・ベルトランによって示されるように、ここで別の答えにスカラ変数と集計と比較(これは両方適用されるいくつかの例外がありますCASEとはCOALESCE):

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

ゼロによる除算エラーを生成します。

これはバグと見なされるべきであり、ルールとしてCOALESCE左から右に解析されます。


6
それは難しいことに同意すること- @JNKこれは(私の懸念はまだ未知のシナリオは、さらに多くあるということです当てはまらない非常に単純なケースを参照して私の答えを参照してください。CASE常に左から右、常に短絡を評価します)。
アーロンバートランド

4
@SQLKiwiのその他の興味深い動作は、次のことを示しています。- SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 1 END), 1);複数回繰り返す。あなたはNULL時々得るでしょう。再試行してくださいISNULL-あなたが得ることは決してないだろうNULL...
アーロン・ベルトラン


@マーティンはい私はそう信じています。ただし、ほとんどのユーザーがその問題を聞いた(または噛まれた)場合を除いて、直感的にわかる動作ではありません。
アーロンバートランド

73

これについてはどうですか-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わけではないということです。ここバグを報告しましたが、「設計どおり」としてクローズされました。Paul Whiteはその後このConnectアイテムを提出し、Fixedとしてクローズされました。それ自体が修正されたためではなく、集計が式の評価順序を変更できるシナリオのより正確な説明でBooks Onlineを更新したためです。最近このことについてもっとブログを書いたCASE

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

あなたがすることができますが、通常は頼りにCASE左から右へと短絡を評価するために、文書で説明したように、あなたが常にそうすることができると言うことは正確ではありません。このページには2つの実証されたケースがありますが、実際にはそうではなく、SQL Serverの公開されているバージョンではどちらのバグも修正されていません。

ここでのEDIT CASE、集計が含まれていなくても、式が期待した順序で評価されない別のケースです(それをやめる必要があります)。


2
別の問題があったかのように見えますし、CASE それは静かに修正されました
マーティン・スミス

IMOこれは、集計値がselectの前に計算されるため(Chave式の評価が保証されないことを証明しません(したがって、having内で使用できるようにするため))。
サルマンA

1
@SalmanA CASE式の評価の順序が保証されていないことを正確に証明することを除いて、これがおそらく他に何をするかはわかりません。例外が発生しているのは、ELSE句に含まれている場合でも、集計が最初に計算されるためです(ドキュメントを参照する場合)には到達しないようにする必要があります。
アーロンバートランド

@AaronBertrand集計は CASEステートメントの前に計算されます(IMOを使用する必要があります)。改訂されたドキュメントは、CASEが評価される前にエラーが発生することを正確に指摘しています。
サルマンA

@SalmanAカジュアルな開発者に対して、CASE式が記述された順序で評価されないことを示しています。 tに達しました。このページの他のすべての例に対する議論もありますか?
アーロンバートランド

37

これについての私の見解は、ドキュメントが、CASEを短絡させるという意図があることを合理的に明らかにしているということです。アーロンが言及しているように、これが常に真実であるとは限らないことが示されたいくつかのケースがありました(ha!)。

これまでのところ、これらはすべてバグとして認識され、修正されました-必ずしも今日購入して修正できるSQL Serverのバージョンではありません(常に折りたたまれているバグはまだ累積的な更新プログラムに達していません)。Itzik Ben-Ganによって最初に報告された最新の潜在的なバグは、まだ調査されていません(Aaronまたは私はまもなくConnectに追加します)。

元の質問に関連して、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);

COALESCEフォームはしばしばNULLを返します。詳細はhttps://connect.microsoft.com/SQLServer/feedback/details/546437/coalesce-subquery-1-may-return-nullにあります。

オプティマイザー変換と共通式の追跡に関する実証された問題は、すべての状況でCASEが短絡することを保証することが不可能であることを意味します。今日、そのための再現はありませんが、パブリックショープランの出力を調べることで動作を予測することすらできない場合が考えられます。

要約すると、CASEが一般的に短絡することは合理的に確信できると思います(特に、適度なスキルを持つ人が実行計画を検査し、実行計画が計画ガイドまたはヒントで「実施」されている場合)絶対的な保証であるため、式をまったく含まないSQLを記述する必要があります。

それほど満足できる状況ではないと思います。


18

CASE/ COALESCE短絡しない別のケースに出くわしました。次のTVFは1、パラメーターとして渡されるとPK違反を発生させます。

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バージョンの計画は以下のとおりです。

予定

このクエリの書き換えは、問題を回避するようです

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


8

もう一つの例

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)

のショーT2読まれています。からの値T2は実際には必要ありませんが。

もちろん、これはそれほど驚くことではありませんが、セットベースの宣言型言語で短絡とはどういう意味かという問題が発生する場合にのみ、反例のリポジトリに追加する価値があると考えました。


7

私はあなたが考慮しなかったかもしれない戦略に言及したかったです。ここでは一致しない場合がありますが、役に立つ場合があります。この変更によりパフォーマンスが向上するかどうかを確認します。

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

基本的に、これはテーブルを「ハード」に結合する手法ですが、行を結合するタイミングに関する条件を含みます。私の経験では、これは時々実行計画を本当に助けました。


3

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

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

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

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

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


@エイドリアンはまだ正しくありません。実行計画を見ると、サブクエリはしばしば非常に賢くJOINに変換されることがわかります。サブクエリ全体を行ごとに繰り返し実行する必要があると仮定するのは、単なる思考実験のエラーですが、これは、スキャン付きのネストされたループ結合が選択された場合に効果的に発生します。
エリック

3

実際の標準では、すべてのWHEN句(およびELSE句)を解析して、式全体のデータ型を決定する必要があるとされています。エラーの処理方法を決定するために、古いメモをいくつか取得する必要があります。しかし、ちょっと手間がかかりますが、1/0は整数を使用するので、エラーではあると思います。整数データ型のエラーです。合体リストにnullしか含まれていない場合、データ型を決定するのは少し難しくなります。これは別の問題です。

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