Оптимізатор запитів SQL Server може поєднувати повторені обчислені значення в одному операторі Compute Scalar. Чи буде це робити чи ні, залежить від калькуляції плану запиту та властивостей розрахункової вартості. Як і очікувалося, це не зробить для обчислених значень, які не є детермінованими, а кілька винятків, таких як RAND(). Це також не буде робити це для визначених користувачем функцій.
Почну з прикладу функції, визначеної користувачем. Ось чудовий приклад функції, визначеної користувачем:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
Я також хочу створити таблицю і помістити в неї 100 рядків:
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_FUNCTIONФункція determistic. Скільки разів він буде виконаний для наступного запиту?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
На основі плану запитів це буде виконано один раз для кожного рядка або 100 разів:

SQL Server 2016 представив DMV sys.dm_exec_function_stats . Ми можемо зробити знімки цього DMV, щоб побачити, скільки разів UDF виконується за допомогою запиту.
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
Результат цього - 100, тому функція виконувалася 100 разів.
Спробуємо ще один простий запит:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
План запитів передбачає, що функція буде виконуватися в 200 разів:

Результати sys.dm_exec_function_statsговорять про те, що функцію виконували 200 разів.
Зауважте, що ви не завжди можете використовувати план запитів, щоб визначити, скільки разів виконується обчислювальний скаляр. Наступна цитата з " Обчислити скаляри, вирази та результати виконання плану ":
Це змушує людей думати, що Compute Scalar поводиться як більшість інших операторів: коли рядки проходять через нього, результати потоку будь-яких обчислень, які містить Compute Scalar, додаються до потоку. Це взагалі не відповідає дійсності. Незважаючи на назву, Compute Scalar не завжди що-небудь обчислює, і не завжди містить одне скалярне значення (це може бути, наприклад, вектор, псевдонім або навіть булевий предикат). Частіше за все обчислювальний скаляр просто визначає вираз; власне обчислення відкладається, поки щось не пізніше в плані виконання не потребує результату.
Спробуємо ще один приклад. Для наступного запиту я сподіваюся, що UDF обчислюється один раз:
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;
План запитів передбачає, що він буде обчислений одноразово:

Однак DMV розкриває правду. Обчислювальний скаляр відкладається до тих пір, поки це не потрібно, що є в операторі з'єднання. Оцінюється 100 разів.
Ви також запитали, що ви можете зробити, щоб заохотити оптимізатор, щоб уникнути повторного перерахунку одного і того ж виразу кілька разів. Найкраще, що ви можете зробити, це уникати використання скалярних АДС у своєму коді. Поза цим питанням є ряд питань щодо продуктивності, включаючи надуття грантів пам’яті, змушування запускати весь запит MAXDOP 1, неправильні оцінки кардинальності та призводити до додаткового використання процесора. Якщо вам потрібно використовувати UDF і значення цього UDF є постійною, ви можете обчислити його поза запитом і помістити його в локальну змінну.
Для запитів без UDF можна спробувати уникнути написання виразів, які повертають один і той же результат, але не вводяться точно так само. Для цього наступного прикладу я використовую загальнодоступну базу даних AdventureworksDW2016CTP3, але дійсно будь-яка база даних буде робити. Скільки разів буде COUNT(*)обчислено цей запит?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Для цього запиту ми можемо визначити це, подивившись на оператора Hash Match (агрегат).

COUNT(*)Обчислюється один раз для кожного унікального значення OrderDateKey. Включення цього ORDER BYпункту не спричиняє його обчислення вдвічі. План виконання ви можете подивитися тут .
Тепер розглянемо запит, який дасть ті самі результати, але записується по-іншому:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Оптимізатор запитів недостатньо розумний для їх поєднання, тому буде проведена додаткова робота:
