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番目の引数を読み取ります(使用されませんが)。
いいえ。ここに簡単なテストがあります:
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によって示されているように、スカラー変数および集計と比較するときにいくつかの例外があります(これはCASE
とCOALESCE
の両方に適用されます):
DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;
ゼロ除算エラーが生成されます。
これはバグと考える必要があり、原則としてCOALESCE
は左から右に解析されます。
これはどうですか-だった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
式は、集計が含まれていなくても、期待する順序で評価されません。
ドキュメントは、意図が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を作成する必要があります。
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
バージョンの計画は以下のとおりです。
このクエリの書き換えにより、問題が回避されたようです
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)
どちらが計画を与える
もう一つの例
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
の値は実際には必要ありませんが。
もちろん、これは驚くべきことではありませんが、セットベースの宣言型言語で短絡が何を意味するかという問題が発生する場合にのみ、カウンターサンプルリポジトリに追加する価値があると思いました。
私はあなたが考慮しなかったかもしれない戦略に言及したかっただけです。ここではマッチしないかもしれませんが、時には重宝します。この変更により、パフォーマンスが向上するかどうかを確認します。
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
基本的に、これはテーブルを「ハード」に結合する手法ですが、行を結合する条件を含めます。私の経験では、これは実行計画に非常に役立ちました。
実際の標準では、式のデータ型全体を決定するには、すべてのWHEN句(およびELSE句)を解析する必要があるとされています。エラーがどのように処理されるかを判断するには、古いメモをいくつか取り出さなければなりません。しかし、手に負えないほど、1/0は整数を使用するので、それはエラーであると思います。整数データ型のエラーです。合体リストにnullしかない場合は、データ型を判別するのが少し難しく、それが別の問題です。
いいえ、そうではありません。 c.FirstName
がNULL
の場合にのみ実行されます。
ただし、自分で試してみてください。実験。あなたのサブクエリは長いと言っていました。基準。これについてあなた自身の結論を導き出してください。
実行中のサブクエリに対する@Aaronの回答はより完全です。
ただし、まだクエリをやり直してLEFT JOIN
を使用する必要があると思います。ほとんどの場合、サブクエリは、LEFT JOIN
sを使用するようにクエリを修正することで削除できます。
サブクエリを使用する際の問題は、メインクエリの結果セットの各行に対してサブクエリが実行されるため、ステートメント全体の実行が遅くなることです。