Optymalizator zapytań SQL Server może łączyć powtarzane obliczone wartości w jednym operatorze obliczania skalarnego. To, czy to zrobi, zależy od kosztu planu zapytania i właściwości obliczonej wartości. Zgodnie z oczekiwaniami nie zrobi tego w przypadku wartości obliczeniowych, które nie są deterministyczne, z kilkoma wyjątkami takimi jak RAND(). Nie zrobi tego również w przypadku funkcji zdefiniowanych przez użytkownika.
Zacznę od przykładu funkcji zdefiniowanej przez użytkownika. Oto doskonały przykład funkcji zdefiniowanej przez użytkownika:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
Chcę również utworzyć tabelę i umieścić w niej 100 wierszy:
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;
dbo.NULL_FUNCTIONFunkcja jest determistic. Ile razy będzie wykonywany dla następującego zapytania?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
Na podstawie planu zapytań zostanie to wykonane raz dla każdego wiersza lub 100 razy:

SQL Server 2016 wprowadził DMV sys.dm_exec_function_stats . Możemy zrobić migawki tego DMV, aby zobaczyć, ile razy UDF jest wykonywane przez zapytanie.
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
Wynikiem tego jest 100, więc funkcja została wykonana 100 razy.
Spróbujmy innego prostego zapytania:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
Plan zapytań sugeruje, że funkcja zostanie wykonana 200 razy:

Wyniki sys.dm_exec_function_statssugerują, że funkcja została wykonana 200 razy.
Pamiętaj, że nie zawsze możesz użyć planu zapytań, aby dowiedzieć się, ile razy skalar obliczeniowy jest wykonywany. Poniższy cytat pochodzi z „ Obliczenia skalarów, wyrażeń i wydajności planu wykonania ”:
To powoduje, że ludzie myślą, że Compal Scalar zachowuje się jak większość innych operatorów: gdy przepływają przez niego rzędy, wyniki wszystkich obliczeń zawartych w Compal Scalar są dodawane do strumienia. Zasadniczo nie jest to prawdą. Mimo nazwy Compal Scalar nie zawsze niczego oblicza i nie zawsze zawiera pojedynczą wartość skalarną (może to być na przykład wektor, alias, a nawet predykat logiczny). Skalar obliczeniowy najczęściej definiuje wyrażenie; faktyczne obliczenia są odraczane do momentu, gdy coś później w planie wykonania będzie wymagało wyniku.
Spróbujmy innego przykładu. W przypadku następującego zapytania mam nadzieję, że UDF jest obliczany jeden raz:
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;
Plan zapytań sugeruje, że zostanie obliczony jeden raz:

Jednak DMV ujawnia prawdę. Skalar obliczeniowy jest odraczany, dopóki nie jest potrzebny, czyli w operatorze łączenia. Jest oceniany 100 razy.
Zapytałeś również, co możesz zrobić, aby zachęcić optymalizator do unikania wielokrotnego ponownego obliczania tego samego wyrażenia. Najlepszą rzeczą, jaką możesz zrobić, to uniknąć skalarnego UDF w kodzie. Mają one szereg problemów z wydajnością poza tym pytaniem, w tym nadmuchiwanie przydziałów pamięci, zmuszanie do uruchomienia całego zapytania MAXDOP 1, złe oszacowanie liczności i prowadzące do dodatkowego wykorzystania procesora. Jeśli potrzebujesz użyć UDF, a wartość tego UDF jest stała, możesz obliczyć go poza zapytaniem i umieścić w zmiennej lokalnej.
W przypadku zapytań bez UDF można spróbować uniknąć pisania wyrażeń, które zwracają ten sam wynik, ale nie są wpisywane dokładnie w ten sam sposób. W następnym przykładzie korzystam z publicznie dostępnej bazy danych AdventureworksDW2016CTP3, ale tak naprawdę każda baza danych będzie wystarczająca. Ile razy zostanie COUNT(*)obliczone dla tego zapytania?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
W przypadku tego zapytania możemy to zrozumieć, patrząc na operator dopasowania (agregacji) mieszania.

COUNT(*)Jest obliczana raz dla każdej unikatowej wartości OrderDateKey. Dołączenie ORDER BYklauzuli nie powoduje dwukrotnego obliczenia. Można zobaczyć plan wykonania tutaj .
Teraz rozważ zapytanie, które zwróci dokładnie te same wyniki, ale zostało napisane w inny sposób:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Optymalizator zapytań nie jest wystarczająco inteligentny, aby je połączyć, więc zostaną wykonane dodatkowe prace:
