【问题标题】:How to use a CTE statement in a table-valued function in SQL Server如何在 SQL Server 的表值函数中使用 CTE 语句
【发布时间】:2013-02-11 17:59:13
【问题描述】:

我了解到某些版本的 Microsoft OLE DB Provider for SQL Server(主要在 Windows XP 上)不支持WITH 语句。因此,我决定将我的 SQL 语句移动到一个表值函数中,并从我的应用程序中调用它。现在,我被困住了。我应该如何将INSERT INTO 语句与WITH 一起使用?这是到目前为止我提供的代码,但 SQL Server 不喜欢它... :-(

CREATE FUNCTION GetDistributionTable 
(
    @IntID int,
    @TestID int,
    @DateFrom datetime,
    @DateTo datetime
)
RETURNS 
@Table_Var TABLE 
(
    [Count] int, 
    Result float
)
AS
BEGIN
INSERT INTO @Table_Var ([Count], Result) WITH T(Result)
     AS (SELECT ROUND(Result - AVG(Result) OVER(), 1)
         FROM RawResults WHERE IntID = @IntID AND DBTestID = @TestID AND Time >= @DateFrom AND Time <= @DateTo)
SELECT COUNT(*) AS [Count],
       Result
FROM   T
GROUP  BY Result

    RETURN 
END
GO

【问题讨论】:

    标签: sql-server sql-server-2008-r2 common-table-expression


    【解决方案1】:

    表值函数中 CTE 的语法为:

    CREATE FUNCTION GetDistributionTable 
    (
        @IntID int,
        @TestID int,
        @DateFrom datetime,
        @DateTo datetime
    )
    RETURNS TABLE
    AS
    RETURN  
    (
        WITH cte AS
        (
            SELECT ROUND(Result - AVG(Result) OVER(), 1) Result
            FROM   RawResults 
            WHERE  IntID = @IntID 
            AND    DBTestID = @TestID 
            AND    Time >= @DateFrom 
            AND Time <= @DateTo    
        )
    
        SELECT  COUNT(*) AS [Count],
                Result
        FROM    cte
        GROUP  BY 
                Result
    )
    GO
    

    如果可能,您也可以省略 CTE(WITH 语句),而是创建一个使用子查询的内联表值函数:

    CREATE FUNCTION GetDistributionTable 
    (
        @IntID int,
        @TestID int,
        @DateFrom datetime,
        @DateTo datetime
    )
    RETURNS TABLE
    AS
    RETURN  
    (
        SELECT  COUNT(*) AS [Count],
                Result
        FROM    (
                     SELECT ROUND(Result - AVG(Result) OVER(), 1) Result
                     FROM   RawResults 
                     WHERE  IntID = @IntID 
                     AND    DBTestID = @TestID 
                     AND    Time >= @DateFrom 
                     AND Time <= @DateTo    
        ) t
        GROUP  BY 
                Result
    )
    GO
    

    您的示例似乎使用了多语句 TVF(插入和选择),当您有选择时尝试使用内联 TVF,因为多语句 TVF 会阻止查询优化器选择更好的执行计划(解释了性能差异here)

    【讨论】:

    • 如果您的 CTE 是递归的,您可能无法将其重写为子查询表单,因此 CTE 表单可能不仅仅是一个简单的口味问题。当然,如果您不小心,递归 CTE 可能会使优化器失效:blogs.msdn.com/b/sqlcat/archive/2011/04/28/…>
    • 所以习惯用;前面加上cte的一部分。
    • @JSS - SQL Server 中的语句应该以分号结束。到目前为止,这仅在有限的区域内强制执行,而不会破坏某些结构的向后兼容性 - 尤其是较新的结构 - 要求前面的语句以分号终止。 CTE 只是其中的一个例子。在 CTE 前面加上分号是避免这种语言要求的一种有点笨拙的方法 - 在这种情况下,如果没有先前的语句需要终止,它会导致混淆。始终如一地终止您的陈述可以避免这种情况。
    • 只是为了记住一些有趣的事情:根据此链接,我们不能在 udf 中使用 maxrecursion 0:social.msdn.microsoft.com/Forums/sqlserver/en-US/…
    • @RichardAbey-Nesbit 值得注意的是,在单语句UDF(最后一个括号之前)的结尾查询中添加分号将被解释为多语句并导致错误。
    【解决方案2】:
    CTE with if else in UDF 
    
    USE [SchoolDB]
    GO
    
    /****** Object:  UserDefinedFunction [dbo].[GetDistributionTable]    Script Date: 24-08-2019 05:17:55 PM ******/
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    
    
    
    
    alter  FUNCTION [dbo].[GetDistributionTable] 
    (
       @DepartmentName varchar(50) = 'Production'
    )
    RETURNS 
    @Table_Var TABLE 
    (   NUM int IDENTITY(1,1),
        [ParentEmployeeKey] int, 
        Result float
    )
    AS
    BEGIN
    Declare @Table_Vars table
    (
        [ParentEmployeeKey] int, 
        Result float
    );
    insert into @Table_Vars([ParentEmployeeKey])
    select COUNT(1) Result from 
            [SchoolDB].[dbo].[DimEmployee] where DepartmentName = @DepartmentName
    if(@@rowcount >0)
    begin
    WITH T 
        AS (    
            select [ParentEmployeeKey],COUNT(1) Result from 
            [SchoolDB].[dbo].[DimEmployee] where DepartmentName = @DepartmentName
            group by [ParentEmployeeKey]
       )
      INSERT INTO @Table_Var ([ParentEmployeeKey], Result)
      SELECT COUNT(*) AS [Count],
           Result
      FROM   T
      GROUP  BY Result
    end
    else
    
     WITH T 
        AS (    
            select [ParentEmployeeKey],COUNT(1) Result from 
            [SchoolDB].[dbo].[DimEmployee] where DepartmentName = @DepartmentName
            group by [ParentEmployeeKey]
       )
      INSERT INTO @Table_Var ([ParentEmployeeKey], Result)
      SELECT COUNT(*) AS [Count],
           Result
      FROM   T
      GROUP  BY Result
      RETURN 
    END
    
    
    
    GO
    

    【讨论】:

      【解决方案3】:

      喜欢这个..

      CREATE FUNCTION GetDistributionTable 
      (
          @IntID int,
          @TestID int,
          @DateFrom datetime,
          @DateTo datetime
      )
      RETURNS 
      @Table_Var TABLE 
      (
          [Count] int, 
          Result float
      )
      AS
      BEGIN
        WITH T 
          AS (    
              select Ticket_Id,COUNT(1) Result from 
              Customer_Survey
              group by MemberID,SiteId,Ticket_Id
         )
        INSERT INTO @Table_Var ([Count], Result)
        SELECT COUNT(*) AS [Count],
             Result
        FROM   T
        GROUP  BY Result
        RETURN 
      END
      GO
      

      【讨论】:

      • 如果可能的话,应该同时测试您展示的多语句函数以及单语句 RETURNS TABLE 函数,因为后者可以内联,因此有时具有更好的性能(请参阅 Ivan G 答案底部的链接)。然而,像往常一样,这一切都取决于--有时,虽然很少,但使用多语句功能会更快。
      猜你喜欢
      • 2016-02-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-19
      相关资源
      最近更新 更多