Функция многозначной таблицы с оценкой по сравнению с встроенной табличной функцией

Несколько примеров, которые нужно показать, просто:

Оценка встроенной таблицы

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

Таблица с несколькими отчетами

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

Есть ли преимущество в использовании одного типа (в строке или нескольких операторов) по сравнению с другим? Существуют ли определенные сценарии, когда один лучше другого или различия являются чисто синтаксическими? Я понимаю, что два примера запросов делают разные вещи, но есть ли причина, по которой я буду писать их таким образом?

Чтение о них и преимущества/отличия на самом деле не были объяснены.

Ответ 1

Изучая комментарий Мэтта, я пересмотрел свое первоначальное выражение. Он верен, будет отличаться производительность между встроенной табличной функцией (ITVF) и многозначной табличной функцией (MSTVF), даже если они оба просто выполняют инструкцию SELECT. SQL Server будет рассматривать ITVF несколько как VIEW, поскольку он рассчитает план выполнения, используя последнюю статистику по рассматриваемым таблицам. MSTVF эквивалентен заполнению всего содержимого вашего оператора SELECT переменной таблицы и последующим присоединением к ней. Таким образом, компилятор не может использовать статистику таблиц в таблицах в MSTVF. Итак, при прочих равных условиях (которые они редко бывают), ITVF будет работать лучше, чем MSTVF. В моих тестах разница в производительности во время завершения была незначительной, однако с точки зрения статистики это было заметно.

В вашем случае две функции не являются функционально эквивалентными. Функция MSTV выполняет дополнительный запрос каждый раз, когда он вызывается и, что самое важное, фильтрует идентификатор клиента. В большом запросе оптимизатор не сможет использовать преимущества других типов соединений, так как ему нужно будет вызвать функцию для каждого переданного clientId. Однако, если вы повторно написали свою функцию MSTV следующим образом:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

В запросе оптимизатор сможет вызвать эту функцию один раз и построить лучший план выполнения, но он все равно не будет лучше, чем эквивалентный, непараметрированный ITVS или VIEW.

ITVF должны быть предпочтительнее, чем MSTVF, когда это возможно, потому что типы данных, нулеустойчивость и сопоставление из столбцов в таблице, тогда как вы объявляете эти свойства в многозначной табличной функции, и, что важно, вы получите лучшие планы выполнения из ITVF. По моему опыту, я не нашел много обстоятельств, когда ITVF был лучшим вариантом, чем VIEW, но пробег может отличаться.

Благодаря Мэтту.

Дополнение

Поскольку я видел это недавно, вот отличный анализ, проведенный Уэйн Шеффилд, сравнивающий разницу в производительности между функциями Inline Table Valued и функциями Multi-Statement.

Его исходное сообщение в блоге.

Копировать в SQL Server Central

Ответ 2

Внутренне SQL Server рассматривает встроенную функцию, связанную с таблицей, так же, как и представление и рассматривает функцию таблицы с несколькими операторами, аналогичную тому, как она хранится в процедуре.

Когда встроенная функция табличной оценки используется как часть внешнего запроса, процессор запросов расширяет определение UDF и генерирует план выполнения, который обращается к базовым объектам, используя индексы на этих объектах.

Для многозначной функции с табличной таблицей создается план выполнения для самой функции и сохраняется в кеше плана выполнения (как только функция была выполнена в первый раз). Если многозначные табличные функции используются как часть более крупных запросов, то оптимизатор не знает, что возвращает функция, и поэтому делает некоторые стандартные предположения - по сути, предполагается, что функция вернет одну строку и что возвращается функция будет доступна с помощью сканирования таблицы по таблице с одной строкой.

В тех случаях, когда функции многозначных табличных значений могут плохо выполняться, они возвращают большое количество строк и объединяются во внешних запросах. Проблемы с производительностью в первую очередь сводятся к тому, что оптимизатор подготовит план, предполагающий возврат одной строки, что не обязательно будет самым подходящим планом.

Как правило, мы обнаружили, что по возможности встроенные функции, связанные с таблицей, следует использовать в предпочтении многозадачным (когда UDF будет использоваться как часть внешнего запроса) из-за этих потенциальных проблем производительности.

Ответ 3

Есть еще одна разница. Встроенная функция, ориентированная на таблицу, может быть вставлена, обновлена ​​и удалена из - как вид. Аналогичные ограничения применяются - не могут обновлять функции с помощью агрегатов, не могут обновлять вычисленные столбцы и т.д.

Ответ 4

Ваши примеры, я думаю, очень хорошо отвечают на вопрос. Первая функция может быть выполнена как один выбор, и это хорошая причина использовать встроенный стиль. Второе, вероятно, можно было бы сделать как один оператор (используя подзапрос, чтобы получить максимальную дату), но некоторые кодеры могут считать его более легким для чтения или более естественным делать это в нескольких заявлениях, как вы это делали. Некоторые функции просто не могут быть выполнены в одном выражении, поэтому требуется версия с несколькими операторами.

Я предлагаю использовать простейший (встроенный), когда это возможно, и, при необходимости, используя многопозиционные заявления (очевидно) или когда личные предпочтения/удобочитаемость заставляют его совершать дополнительные набрав.

Ответ 6

Я не проверял это, но функция с несколькими операторами кэширует набор результатов. Могут быть случаи, когда оптимизатор слишком много встроит функцию. Например, предположим, что у вас есть функция, которая возвращает результат из разных баз данных в зависимости от того, что вы передаете как "Номер компании". Как правило, вы можете создать представление с объединением all, а затем фильтровать по номеру компании, но я обнаружил, что иногда sql-сервер откатывает весь объединение и не достаточно умен, чтобы вызвать выбранное. Табличная функция может иметь логику для выбора источника.

Ответ 7

Другим случаем использования многострочной функции было бы обойти сервер sql от нажатия предложения where.

Например, у меня есть таблица с именами таблиц, и некоторые имена таблиц отформатированы как C05_2019 и C12_2018, и все таблицы, отформатированные таким образом, имеют одинаковую схему. Я хотел объединить все эти данные в одну таблицу и разобрать 05 и 12 в столбец CompNo и 2018,2019 в столбец год. Тем не менее, есть другие таблицы, такие как ACA_StupidTable, которые я не могу извлечь CompNo и CompYr и получить ошибку преобразования, если я попытаюсь. Итак, мой запрос состоял из двух частей: внутреннего запроса, который возвращал только таблицы, отформатированные как "C_______", тогда внешний запрос выполнял преобразование подстроки и int. то есть приведение (подстрока (2, 2) как int) как CompNo. Все выглядит хорошо, за исключением того, что сервер sql решил установить функцию Cast до того, как результаты будут отфильтрованы, и поэтому я получаю ошибку преобразования. Табличная функция с множеством операторов может предотвратить это, поскольку в основном это "новая" таблица.

Ответ 8

если вы собираетесь выполнить запрос, вы можете присоединиться к своей функции Inline Table Valued, например:

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

он будет нести небольшие накладные расходы и работать нормально.

если вы попытаетесь использовать таблицу Multi Statement, оцененную в аналогичном запросе, у вас будут проблемы с производительностью:

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

потому что вы будете выполнять функцию 1 раз для каждой возвращаемой строки, так как набор результатов будет большим, он будет работать медленнее и медленнее.