複数ステートメントのテーブル値関数は、テーブル変数で結果を返します。
これらの結果は再利用されますか、それとも呼び出されるたびに関数は常に完全に評価されますか?
複数ステートメントのテーブル値関数は、テーブル変数で結果を返します。
これらの結果は再利用されますか、それとも呼び出されるたびに関数は常に完全に評価されますか?
回答:
複数ステートメントのテーブル値関数(msTVF)の結果は、ステートメント(または接続)間でキャッシュまたは再利用されることはありませんが、msTVFの結果を同じステートメント内で再利用できる方法がいくつかあります。その範囲で、msTVFは呼び出されるたびに再入力されるとは限りません。
この(故意に非効率的な)msTVFは、各行にタイムスタンプを付けて、指定された範囲の整数を返します。
IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table
(
n integer PRIMARY KEY,
ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
WHILE @From <= @To
BEGIN
INSERT @T (n)
VALUES (@From);
SET @From = @From + 1;
END;
RETURN;
END;
関数呼び出しへのすべてのパラメーターが定数(またはランタイム定数)である場合、実行プランはテーブル変数の結果を1回取り込みます。プランの残りの部分は、テーブル変数に何度もアクセスできます。テーブル変数の静的な性質は、実行計画から認識できます。例えば:
SELECT
IR.n,
IR.ts
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
IR.n;
次のような結果を返します。
実行計画は次のとおりです。
Sequenceオペレーターは、最初にTable Valued Functionオペレーターを呼び出します。これは、テーブル変数を取り込みます(このオペレーターは行を返さないことに注意してください)。次に、Sequenceは2番目の入力を呼び出し、テーブル変数の内容を返します(この場合、クラスター化インデックススキャンを使用)。
プランが「静的な」テーブル変数の結果を使用していることは、シーケンスの下のテーブル値関数演算子です。テーブル変数は、プランの残りの部分を実行する前に一度完全に設定する必要があります。
複数回アクセスされているテーブル変数の結果を表示するには、1から5までの番号が付けられた行を持つ2番目のテーブルを使用します。
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
CREATE TABLE dbo.T (i integer NOT NULL);
INSERT dbo.T (i)
VALUES (1), (2), (3), (4), (5);
そして、このテーブルを関数に結合する新しいクエリ(これもとして記述できますAPPLY
):
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
ON IR.n = T.i;
結果は次のとおりです。
実行計画:
前と同様に、シーケンスはテーブル変数msTVFの結果を最初に入力します。次に、ネストされたループを使用して、テーブルT
の各行をmsTVF結果の行に結合します。関数定義には有用なインデックスがテーブル変数に含まれているため、インデックスシークを使用できます。
重要な点は、msTVFへのパラメーターが定数(変数とパラメーターを含む)であるか、実行エンジンによってステートメントの実行時定数として扱われる場合、計画にはmsTVFテーブル変数結果の2つの別個の演算子が含まれることです。表; 別の方法で結果にアクセスし、場合によってはテーブルに複数回アクセスし、場合によっては関数定義で宣言されたインデックスを使用します。
相関パラメーター(外部参照)または非定数関数パラメーターが使用されている場合の違いを強調するT
ために、テーブルの内容を変更して、関数がさらに多くの作業を行えるようにします。
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50001), (50002), (50003), (50004), (50005);
次の変更されたクエリT
は、関数パラメータの1つでテーブルへの外部参照を使用するようになりました。
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
このクエリは、次のような結果を返すのに約8秒かかります。
columnの行間の時間差に注意してくださいts
。このWHERE
節は、適切なサイズの出力の最終結果を制限しますが、非効率的な関数は、テーブル変数に50,000の行(i
from tableの相関値に応じて)を設定するのにまだ時間がかかりますT
。
実行計画は次のとおりです。
順序演算子がないことを注意してください。現在、ネストされたループ結合の各反復でテーブル変数にデータを入力し、その行を返す単一のテーブル値関数演算子があります。
明確にするために、テーブルTに5行しかない場合、テーブル値関数演算子は5回実行されます。最初の反復で50,001行、2回目の反復で50,002などを生成します。テーブル変数は、反復間で「破棄」(切り捨て)されるため、5つの呼び出しはそれぞれ完全な母集団です。これが非常に遅い理由であり、各行が結果に表示されるのとほぼ同じ時間がかかります。
サイドノート:
当然、上記のシナリオは、msTVFが各反復で多数の行を取り込むときにパフォーマンスがどれほど低下する可能性があるかを示すために意図的に考案されています。
賢明上記のコードの実装は、設定します両方にmsTVFパラメータをi
、重複削除WHERE
句。テーブル変数は、繰り返しごとに切り捨てられ、再入力されますが、毎回1行のみです。
前のステップで変数i
から最小値と最大値を取得T
し、変数に保存することもできます。相関パラメーターの代わりに変数を使用して関数を呼び出すと、前述のように「静的な」テーブル変数パターンを使用できます。
Sequence静的パターンを使用できない場合、元の質問にもう一度戻ると、SQL Server は、ネストされたループ結合の前回の反復以降に相関パラメーターが変更されていない場合、msTVFテーブル変数の切り捨てと再入力を回避できます。
これを実証するために、の内容T
を5つの同一の i
値に置き換えます。
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50005), (50005), (50005), (50005), (50005);
再び相関パラメーターを持つクエリ:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
今回は、結果が約1.5秒で表示されます。
各行の同一のタイムスタンプに注意してください。テーブル変数にキャッシュされた結果は、相関値i
が変更されない後続の反復で再利用されます。結果を再利用すると、毎回50,005行を挿入するよりもはるかに高速です。
実行計画は以前と非常によく似ています。
主な違いは、であり、実際の再バインドし、実際の巻き戻しテーブル値関数演算子の特性:
相関パラメーターが変化しない場合、SQL Serverはテーブル変数の現在の結果を再生(巻き戻し)できます。相関関係が変更されると、SQL Serverはテーブル変数を切り捨てて、再入力(再バインド)する必要があります。1回目の再バインドは最初の反復で発生します。の値T.i
は変更されないため、4回の後続の反復はすべて巻き戻しです。