Табличная функция с несколькими утверждениями возвращает свой результат в табличной переменной.
Эти результаты когда-либо повторно используются, или функция всегда полностью оценивается каждый раз, когда она вызывается?
Табличная функция с несколькими утверждениями возвращает свой результат в табличной переменной.
Эти результаты когда-либо повторно используются, или функция всегда полностью оценивается каждый раз, когда она вызывается?
Ответы:
Результаты табличной функции с несколькими утверждениями (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;
Если все параметры вызова функции являются константами (или константами времени выполнения), план выполнения заполнит результат табличной переменной один раз. Оставшаяся часть плана может обращаться к переменной таблицы много раз. Статический характер табличной переменной можно узнать из плана выполнения. Например:
SELECT
IR.n,
IR.ts
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
IR.n;
Возвращает результат, похожий на:
План выполнения:
Оператор Sequence сначала вызывает оператор Table Valued Function, который заполняет табличную переменную (обратите внимание, этот оператор не возвращает строк). Затем последовательность вызывает второй вход, который возвращает содержимое табличной переменной (в этом случае используется сканирование кластерного индекса).
Подсказка о том, что в плане используется «статическая» табличная переменная, представляет собой оператор Table Valued Function под Sequence - переменную таблицы необходимо заполнить один раз, прежде чем можно будет приступить к выполнению оставшейся части плана.
Чтобы показать, что результат табличной переменной доступен более одного раза, мы будем использовать вторую таблицу со строками, пронумерованными от 1 до 5:
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: один для заполнения стол; другой - для доступа к результатам, возможно, к таблице несколько раз, и, возможно, к использованию индексов, объявленных в определении функции.
Чтобы выделить различия, когда используются коррелированные параметры (внешние ссылки) или параметры непостоянных функций, мы изменим содержимое таблицы, T
чтобы у функции было гораздо больше работы:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50001), (50002), (50003), (50004), (50005);
Следующий модифицированный запрос теперь использует внешнюю ссылку на таблицу T
в одном из параметров функции:
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 секунд, чтобы вернуть результаты, как:
Обратите внимание на разницу во времени между строками в столбце ts
. Предложение WHERE
ограничивает конечный результат для вывода разумного размера, но неэффективной функции все еще требуется некоторое время, чтобы заполнить табличную переменную 50 000 нечетными строками (в зависимости от коррелированного значения i
из таблицы T
).
План выполнения:
Обратите внимание на отсутствие оператора Sequence. Теперь есть единственный оператор Table Valued Function, который заполняет табличную переменную и возвращает ее строки на каждой итерации объединения вложенных циклов.
Чтобы быть понятным: всего 5 строк в таблице T, оператор Table Valued Function выполняется 5 раз. Он генерирует 50,001 строк на первой итерации, 50,002 на второй ... и так далее. Переменная таблицы «выбрасывается» (усекается) между итерациями, поэтому каждый из пяти вызовов является полной популяцией. Вот почему это так медленно, и каждая строка занимает примерно одно и то же время, чтобы появиться в результате.
Примечания стороны:
Естественно, вышеописанный сценарий намеренно придуман, чтобы показать, насколько низкой может быть производительность, когда msTVF заполняет много строк на каждой итерации.
Разумное осуществление вышеуказанного кода будет установить оба параметр msTVF к i
, и удалить избыточное WHERE
положение. Переменная таблицы по-прежнему будет обрезаться и повторно заполняться на каждой итерации, но только с одной строкой каждый раз.
Мы также могли бы извлечь минимальные и максимальные i
значения из T
и сохранить их в переменных на предыдущем шаге. Вызов функции с переменными вместо коррелированных параметров позволит использовать шаблон статических переменных таблицы, как отмечалось ранее.
Возвращаясь к исходному вопросу еще раз, когда статический шаблон Sequence не может быть использован, SQL Server может избежать усечения и повторного заполнения табличной переменной msTVF, если ни один из коррелированных параметров не изменился со времени предыдущей итерации соединения с вложенным циклом.
Чтобы продемонстрировать это, мы заменим содержимое на T
пять одинаковых 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 строк каждый раз.
План выполнения выглядит очень похоже на ранее:
Основное различие заключается в свойствах Actual Rebinds и Actual Rewinds оператора Table Valued Function:
Когда коррелированные параметры не изменяются, SQL Server может воспроизвести (перемотать) текущие результаты в табличной переменной. Когда корреляция изменится, SQL Server должен усечь и снова заполнить табличную переменную (перепривязать). Одно повторное связывание происходит на первой итерации; все четыре последующие итерации перематываются, так как значение T.i
не изменяется.