スカラーUDFの結果に対してフィルタリングする必要があるクエリがあります。クエリは単一のステートメントとして送信する必要があるため(UDF結果をローカル変数に割り当てることができません)、TVFを使用できません。スカラーUDFによって引き起こされるパフォーマンスの問題を認識しています。これには、計画全体を連続的に実行すること、過剰なメモリ許可、カーディナリティー推定の問題、インライン化の欠如が含まれます。この質問については、スカラーUDFを使用する必要があると想定してください。
UDF自体は呼び出すのにかなり費用がかかりますが、理論的には、関数を一度計算するだけで済むように、オプティマイザーによってクエリを論理的に実装できます。この質問の非常に単純化された例をモックアップしました。次のクエリは、マシンで実行するのに6152ミリ秒かかります。
SELECT x1.ID
FROM dbo.X_100_INTEGERS x1
WHERE x1.ID >= dbo.EXPENSIVE_UDF();
クエリプランのフィルター演算子は、関数が行ごとに1回評価されたことを示しています。
DDLおよびデータ準備:
CREATE OR ALTER FUNCTION dbo.EXPENSIVE_UDF () RETURNS INT
AS
BEGIN
DECLARE @tbl TABLE (VAL VARCHAR(5));
-- make the function expensive to call
INSERT INTO @tbl
SELECT [VALUE]
FROM STRING_SPLIT(REPLICATE(CAST('Z ' AS VARCHAR(MAX)), 20000), ' ');
RETURN 1;
END;
GO
DROP TABLE IF EXISTS dbo.X_100_INTEGERS;
CREATE TABLE dbo.X_100_INTEGERS (ID INT NOT NULL);
-- insert 100 integers from 1 - 100
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO dbo.X_100_INTEGERS WITH (TABLOCK)
SELECT n FROM Nums WHERE n <= 100;
ここでデシベルフィドルリンクのコードが実行するのに約18秒かかりますが、上記の例のために。
場合によっては、ベンダーから提供されているため、関数のコードを編集できないことがあります。それ以外の場合は、変更を加えることができます。クエリでスカラーUDFを一度だけ評価するにはどうすればよいですか?