次の再現例( 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
_)の下の一部のコードを実行しない場合でも実行するようです。
私の質問:
これは、スカラーUDFのインライン化のバグです(または、スカラーUDFインライン化によってより多く公開されているクエリオプティマイザーのバグです)。 _WITH INLINE = OFF
_を使用して、その関数のインライン展開をオフにすることができます。
定数の代わりに変数を使用すると、もう少し詳細が表示されます
_declare @myYear int = 0
SELECT dbo.Repro(@myYear);
_
Expr1000 = CASE WHEN [@myYear]<>(1990) THEN (1) ELSE (0) END
を定義します[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(datetime,datefromparts([@myYear],(1),(1)),0))
を定義しますこれらの式は、リテラル_0
_から_1
_およびCONVERT_IMPLICIT(datetime,datefromparts((0),(1),(1)),0)
をそれぞれ使用すると簡略化されます。
_datefromparts(0
_は評価時にエラーをスローします。
Expr1002 = CASE WHEN [Expr1000] = (1) THEN (1) ELSE (0) END
を定義しますそして、_Expr1002
_は、ネストされたループ結合(ノード3)で パススルー述語 として使用されます。そのネストされたループの内側では、定数スキャン(ノード7)は列を返しません。
つまり、これは ここでの答え と同じ基本的な問題のように見えます。パススルー述語によって保護されたネストされたループの内側の式が、保護されていない領域に移動されます。