web-dev-qa-db-ja.com

SQL Server 2019が到達できないコードを実行する

次の再現例( fiddle )を考えてみます。

_CREATE FUNCTION dbo.Repro (@myYear int)
RETURNS datetime
AS
BEGIN
    IF @myYear <> 1990
    BEGIN
        RETURN NULL
    END

    DECLARE @firstOfYear datetime;
    SET @firstOfYear = DATEFROMPARTS(@myYear, 1, 1);

    IF DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0
    BEGIN
        RETURN NULL
    END

    RETURN @firstOfYear
END
SELECT dbo.Repro(0);
_

明らかに、その関数は、入力が_1990_の場合は1990年1月1日を返し、それ以外の場合はNULLを返す必要があります。はい、私はDATEDIFF(day, @firstOfYear, @firstOfYear) <> 0が無意味な操作であることを知っています。これは mcve であり、製品コードではなく、潜在的なバグを示しています。

次に、SQL Server 2017およびSQL Server 2019でSELECT dbo.Repro(0)を実行します。

期待される結果NULL

SQL Server 2017での実際の結果NULL

SQL Server 2019での実際の結果

メッセージ289レベル16状態1行1
データタイプの日付を作成できません。一部の引数に無効な値が含まれています。

どうやら、SQL Server 2019は、最初のガード句(_IF @myYear <> 1990_)の下の一部のコードを実行しない場合でも実行するようです。

私の質問:

  • これは予想される動作ですか、それともSQL Server 2019でバグを見つけましたか?
  • これが予想される動作である場合、入力パラメーターを検証するガード句を正しく記述するにはどうすればよいですか?
34
Heinzi

これは、スカラーUDFのインライン化のバグです(または、スカラーUDFインライン化によってより多く公開されているクエリオプティマイザーのバグです)。 _WITH INLINE = OFF_を使用して、その関数のインライン展開をオフにすることができます。

定数の代わりに変数を使用すると、もう少し詳細が表示されます

_declare @myYear int = 0

SELECT dbo.Repro(@myYear);
_

enter image description here

  • ノード5はExpr1000 = CASE WHEN [@myYear]<>(1990) THEN (1) ELSE (0) ENDを定義します
  • ノード2は[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(datetime,datefromparts([@myYear],(1),(1)),0))を定義します

これらの式は、リテラル_0_から_1_およびCONVERT_IMPLICIT(datetime,datefromparts((0),(1),(1)),0)をそれぞれ使用すると簡略化されます。

_datefromparts(0_は評価時にエラーをスローします。

  • ノード6はExpr1002 = CASE WHEN [Expr1000] = (1) THEN (1) ELSE (0) ENDを定義します

そして、_Expr1002_は、ネストされたループ結合(ノード3)で パススルー述語 として使用されます。そのネストされたループの内側では、定数スキャン(ノード7)は列を返しません。

つまり、これは ここでの答え と同じ基本的な問題のように見えます。パススルー述語によって保護されたネストされたループの内側の式が、保護されていない領域に移動されます。

42
Martin Smith