多语句表值函数与内联表值函数

时间:2010-03-31 15:41:36

标签: sql sql-server sql-server-2008 tsql user-defined-functions

要展示的一些例子,只需要注意:

内联表值

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

使用一种类型(内联语句还是多语句)优于另一种类型是否有优势?当一个人比另一个人好或者纯粹是语法上的差异时,是否存在某些情况?我意识到这两个示例查询正在做不同的事情,但我有理由以这种方式编写它们吗?

阅读它们以及优点/差异尚未得到解释。

9 个答案:

答案 0 :(得分:132)

在研究马特的评论时,我修改了原来的陈述。他是正确的,即使它们都只是执行SELECT语句,内联表值函数(ITVF)和多语句表值函数(MSTVF)之间的性能也会有所不同。 SQL Server会将ITVF视为VIEW,因为它将使用有关表的最新统计信息计算执行计划。 MSTVF等效于将SELECT语句的全部内容填充到表变量中,然后加入到该变量中。因此,编译器不能对MSTVF中的表使用任何表统计信息。因此,在所有条件相同的情况下(他们很少),ITVF将比MSTVF表现更好。在我的测试中,完成时间的性能差异可以忽略不计,但从统计的角度来看,它是显而易见的。

在您的情况下,这两个功能在功能上并不相同。 MSTV函数每次调用时都会执行额外的查询,最重要的是,会对客户ID进行过滤。在大型查询中,优化器将无法利用其他类型的连接,因为它需要为每个传递的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更好,但里程可能会有所不同。

感谢Matt。

添加

自从我最近看到这一点以来,Wayne Sheffield做了一个很好的分析,比较了内联表值函数和多语句函数之间的性能差异。

His original blog post.

Copy on SQL Server Central

答案 1 :(得分:27)

在内部,SQL Server将内联表值函数视为一个视图,并将多语句表值函数视为与存储过程类似的函数。

当内联表值函数用作外部查询的一部分时,查询处理器会扩展UDF定义并使用这些对象上的索引生成访问基础对象的执行计划。

对于多语句表值函数,为函数本身创建执行计划并存储在执行计划缓存中(一旦第一次执行该函数)。如果多语句表值函数被用作较大查询的一部分,则优化器不知道函数返回什么,因此做出一些标准假设 - 实际上它假设函数将返回单行,并且返回通过对具有单行的表使用表扫描来访问该函数。

多语句表值函数表现不佳的情况是它们返回大量行并在外部查询中连接起来。性能问题主要取决于优化器将生成一个假定返回单行的计划,这不一定是最合适的计划。

作为一般经验法则,我们发现,由于这些潜在的性能问题,应该使用内联表值函数而不是多语句函数(当UDF将用作外部查询的一部分时)。

答案 2 :(得分:13)

还有另一个区别。可以插入,更新和删除内联表值函数 - 就像视图一样。类似的限制适用 - 无法使用聚合更新函数,无法更新计算列等等。

答案 3 :(得分:3)

我认为你的例子很好地回答了这个问题。第一个函数可以作为单个选择完成,并且是使用内联样式的一个很好的理由。第二个可能是作为单个语句完成的(使用子查询来获取最大日期),但是一些编码器可能会发现它更容易阅读或更自然地在多个语句中执行它。有些函数只是在一个语句中无法完成,因此需要多语句版本。

我建议尽可能使用最简单的(内联),并在必要时(显然)使用多语句,或者当个人偏好/可读性使其成为额外的输入时。

答案 4 :(得分:0)

查看Comparing Inline and Multi-Statement Table-Valued Functions您可以找到好的描述和性能基准

答案 5 :(得分:0)

我还没有测试过,但是多语句功能会缓存结果集。在某些情况下,优化器无法内联函数。例如,假设您有一个函数,该函数根据您作为“公司编号”传递的内容从不同的数据库返回结果。通常,您可以创建一个带有联合的视图,然后按公司编号过滤,但我发现有时sql server会拉回整个联合,并且不够智能,无法调用单选。表函数可以具有选择源的逻辑。

答案 6 :(得分:0)

使用多行功能的另一种情况是避免sql server下推where子句。

例如,我有一个带有表名的表,并且某些表名的格式类似于C05_2019和C12_2018,并且以这种方式格式化的所有表都具有相同的架构。我想将所有数据合并到一个表中,并将05和12解析为CompNo列,将2018,2019解析为year列。但是,还有其他表,例如ACA_StupidTable,我无法提取CompNo和CompYr,如果尝试尝试将得到转换错误。因此,我的查询分为两部分,一个内部查询仅返回格式为'C_______'的表,然后外部查询执行子字符串和int转换。即Cast(Substring(2,2)int)作为CompNo。除了SQL Server决定在过滤结果之前放置我的Cast函数外,其他所有内容看起来都不错,所以我得到了令人难以置信的转换错误。多语句表函数可以阻止这种情况的发生,因为它基本上是一个“新”表。

答案 7 :(得分:0)

也许以一种非常简洁的方式。 ITVF(内联TVF):如果您是DB人,则更多,是一种参数化视图,只需一个SELECT st

MTVF(多语句TVF):开发人员,创建并加载表变量。

答案 8 :(得分:-2)

如果您要进行查询,可以加入您的内联表值函数,如:

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

它会产生很少的开销并且运行良好。

如果您尝试在类似查询中使用多语句表值,则会出现性能问题:

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

因为您将为返回的每一行执行1次函数,因为结果集变大,它将运行得越来越慢。