Das SQL Server-Abfrageoptimierungsprogramm kann wiederholt berechnete Werte in einem einzigen Compute Scalar-Operator kombinieren. Ob dies der Fall ist oder nicht, hängt von der Kalkulation des Abfrageplans und den Eigenschaften des berechneten Werts ab. Wie erwartet wird dies nicht für berechnete Werte durchgeführt, die nicht deterministisch sind, was einige Ausnahmen wie z RAND(). Dies wird auch nicht für benutzerdefinierte Funktionen ausgeführt.
Ich werde mit einem benutzerdefinierten Funktionsbeispiel beginnen. Hier ist ein hervorragendes Beispiel für eine benutzerdefinierte Funktion:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
Ich möchte auch eine Tabelle erstellen und 100 Zeilen einfügen:
CREATE TABLE X_100 (N BIGINT NOT NULL);
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 X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;
Die dbo.NULL_FUNCTIONFunktion ist deterministisch. Wie oft wird es für die folgende Abfrage ausgeführt?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
Basierend auf dem Abfrageplan wird dies einmal für jede Zeile oder 100 Mal ausgeführt:

In SQL Server 2016 wurde die DMV sys.dm_exec_function_stats eingeführt . Wir können Schnappschüsse dieser DMV machen, um zu sehen, wie oft eine UDF von einer Abfrage ausgeführt wird.
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
Das Ergebnis ist 100, daher wurde die Funktion 100 Mal ausgeführt.
Versuchen wir eine andere einfache Abfrage:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
Der Abfrageplan schlägt vor, dass die Funktion 200 Mal ausgeführt wird:

Die Ergebnisse von sys.dm_exec_function_statslegen nahe, dass die Funktion 200 Mal ausgeführt wurde.
Beachten Sie, dass Sie den Abfrageplan nicht immer verwenden können, um herauszufinden, wie oft ein Berechnungsskalar ausgeführt wird. Das folgende Zitat stammt aus " Skalare, Ausdrücke und Leistung des Ausführungsplans berechnen ":
Dies führt dazu, dass die Leute glauben, dass sich Compute Scalar wie die meisten anderen Operatoren verhält: Während Zeilen durch den Scalar fließen, werden die Ergebnisse aller Berechnungen, die der Compute Scalar enthält, zum Stream hinzugefügt. Dies ist im Allgemeinen nicht wahr. Trotz des Namens berechnet Compute Scalar nicht immer etwas und enthält nicht immer einen einzelnen Skalarwert (dies kann beispielsweise ein Vektor, ein Alias oder sogar ein Boolesches Prädikat sein). Meistens definiert ein Compute Scalar einfach einen Ausdruck. Die eigentliche Berechnung wird verschoben, bis etwas später im Ausführungsplan das Ergebnis benötigt.
Versuchen wir ein anderes Beispiel. Für die folgende Abfrage würde ich hoffen, dass die UDF einmal berechnet wird:
WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;
Der Abfrageplan schlägt vor, dass er einmal berechnet wird:

Die DMV enthüllt jedoch die Wahrheit. Der Berechnungsskalar wird verschoben, bis er benötigt wird, was sich im Join-Operator befindet. Es wird 100 mal ausgewertet.
Sie haben auch gefragt, was Sie tun können, um den Optimierer zu ermutigen, den gleichen Ausdruck nicht mehrmals neu zu berechnen. Das Beste, was Sie tun können, ist zu vermeiden, skalare UDFs in Ihrem Code zu verwenden. Diese haben eine Reihe von Leistungsproblemen außerhalb dieser Frage, darunter das Aufblasen von Speicherzuweisungen, das Erzwingen der Ausführung der gesamten Abfrage MAXDOP 1, schlechte Kardinalitätsschätzungen und die zusätzliche CPU-Auslastung. Wenn Sie eine UDF verwenden müssen und der Wert dieser UDF eine Konstante ist, können Sie sie außerhalb der Abfrage berechnen und in eine lokale Variable einfügen.
Bei Abfragen ohne UDFs können Sie vermeiden, Ausdrücke zu schreiben, die dasselbe Ergebnis zurückgeben, aber nicht genau auf dieselbe Weise eingegeben werden. Für dieses nächste Beispiel verwende ich die öffentlich verfügbare AdventureworksDW2016CTP3-Datenbank, aber wirklich jede Datenbank reicht aus. Wie oft wird COUNT(*)für diese Abfrage berechnet?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Für diese Abfrage können wir dies anhand des Operators Hash Match (Aggregat) herausfinden.

Das COUNT(*)wird einmal für jeden eindeutigen Wert von berechnet OrderDateKey. Das Einbeziehen der ORDER BYKlausel führt nicht dazu, dass sie zweimal berechnet wird. Den Ausführungsplan sehen Sie hier .
Stellen Sie sich nun eine Abfrage vor, die genau dieselben Ergebnisse liefert, aber anders geschrieben ist:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Das Abfrageoptimierungsprogramm ist nicht intelligent genug, um sie zu kombinieren. Daher wird zusätzliche Arbeit geleistet:
