Многозначная табличная функция с функцией встроенной таблицы - PullRequest
183 голосов
/ 31 марта 2010

Несколько примеров, чтобы показать, просто incase:

Значение встроенной таблицы

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

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

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

Ответы [ 8 ]

133 голосов
/ 31 марта 2010

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

В вашем случае две функции не являются функционально эквивалентными. Функция MSTV выполняет дополнительный запрос при каждом вызове и, что наиболее важно, фильтрует идентификатор клиента. В большом запросе оптимизатор не сможет воспользоваться преимуществами других типов объединений, так как ему потребуется вызывать функцию для каждого переданного customerId. Однако, если вы переписали свою функцию 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, но пробег может отличаться.

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

Сложение

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

Его оригинальный пост в блоге.

Копирование на SQL Server Central

27 голосов
/ 16 апреля 2010

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

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

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

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

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

13 голосов
/ 25 июля 2011

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

3 голосов
/ 31 марта 2010

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

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

0 голосов
/ 05 апреля 2019

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

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

0 голосов
/ 05 апреля 2019

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

0 голосов
/ 17 февраля 2015

посмотрите на Сравнение встроенных и многозначных табличных функций вы можете найти хорошие описания и показатели производительности

0 голосов
/ 31 марта 2010

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

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

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

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

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

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

...