選択を追加するときに自己参照スカラー関数のネストレベルを超えました


24

目的

自己参照関数のテスト例を作成しようとすると、あるバージョンが失敗し、別のバージョンが成功します。

唯一の違いはSELECT、関数本体に追加されることです。その結果、両方で異なる実行計画が作成されます。


機能する機能

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

関数を呼び出す

SELECT dbo.test5(3);

返品

(No column name)
3

機能しない機能

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

関数を呼び出す

SELECT dbo.test6(3);

または

SELECT dbo.test6(2);

エラーの結果

ストアドプロシージャ、関数、トリガー、またはビューのネストレベルが最大値を超えています(制限32)。

原因を推測する

失敗した関数の推定プランに追加の計算スカラーがあり、呼び出し

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END">

そしてexpr1000

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

32を超える再帰参照を説明できます。

実際の質問

追加SELECTすると、関数自体が何度も呼び出され、無限ループが発生しますが、なぜSELECTこの結果を追加するのですか?


追加情報

推定実行計画

DB <>フィドル

Build version:
14.0.3045.24

compatibility_levels 100および140でテスト済み

回答:


26

これは、プロジェクトの正規化のバグであり、非決定的関数を含むcase式内でサブクエリを使用することで明らかになります。

説明するには、2つのことを前もって注意する必要があります。

  1. SQL Serverはサブクエリを直接実行できないため、サブクエリは常に展開され、またはapplyに変換されます
  2. セマンティクスはCASEするようになっているTHEN場合にのみ式は評価されるべきWHEN句がtrueを返します。

したがって、問題のあるケースで導入された(簡単な)サブクエリは、適用演算子(ネストされたループ結合)になります。2番目の要件を満たすために、SQL Serverは最初に式dbo.test6(1) + dbo.test6(2)を適用の内側に配置します。

ハイライトされた計算スカラー

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

... 結合のパススルー述語CASEによって尊重されるセマンティクスで:

[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)

ループの内側は、パススルー条件がfalse(つまり@i = 3)と評価された場合にのみ評価されます。これはこれまでのところすべて正しいです。計算スカラーネストされたループも名誉参加以下CASE、正しく意味を:

[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

問題は、クエリコンパイルのプロジェクト正規化段階で、それExpr1000が無相関であると認識され、ループ外に移動しても安全であると判断されることです(ナレーター:それはそうではありません)。

移動したプロジェクト

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

これにより、パススルー述語によって実装されるセマンティクスが壊れる* ため、関数は評価されるべきでないときに評価され、無限ループが発生します。

このバグを報告する必要があります。回避策は、式を相関させる(@i式に含める)ことにより、式が適用外に移動するのを防ぐことですが、これはもちろんハックです。プロジェクトの正規化を無効にする方法がありますが、私はそれを公に共有しないように以前に頼まれたので、そうしません。

この問題は、スカラー関数がインライン化されているSQL Server 2019では発生しません。これ、インライン化ロジックが解析済みツリーで直接動作するためです(プロジェクトの正規化のかなり前)。質問の単純なロジックは、非再帰へのインラインロジックによって簡素化できます。

[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))

... 3を返します。

コアの問題を説明する別の方法は次のとおりです。

-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error() 
RETURNS integer 
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
    RETURN 1/0;
END;
GO
DECLARE @i integer = 1;

SELECT
    CASE 
        WHEN @i = 1 THEN 1
        WHEN @i = 2 THEN 2
        WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
        ELSE NULL
    END;

2008 R2から2019 CTP 3.0までのすべてのバージョンの最新ビルドを再現します。

Martin Smithが提供する別の例(スカラー関数なし):

SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))

これには、必要なすべての重要な要素が含まれます。

  • CASE(として内部的に実装ScaOp_IIF
  • 非決定的関数(CRYPT_GEN_RANDOM
  • 実行すべきではないブランチのサブクエリ((SELECT ...)

*厳密には、上記の変換はExpr1000、安全な構造によってのみ参照されるため、評価が正しく延期された場合でも正しい可能性があります。

[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

...ただし、これには内部ForceOrderフラグ(クエリヒントではない)が必要です。これも設定されていません。いずれにしても、プロジェクトの正規化によって適用されるロジックの実装正しくないか不完全です。

SQL ServerのAzureフィードバックサイトのバグレポート

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