【问题标题】:Dynamic SQL in Function / While Loop Insert into Table Variable with Dynamic SQL函数/While 循环中的动态 SQL 使用动态 SQL 插入表变量
【发布时间】:2017-09-30 07:44:27
【问题描述】:

我已经编写了以下查询...但是由于几个原因,我很难将其转换为函数...(1) 因为它包含动态 SQL,我不确定函数是否允许动态 SQL,以及 (2) 因为我使用临时表来存储输出数据。我尝试将其转换为表变量,但没有成功。查询的重点是获取任意两个日期时间范围和一个增量类型(月、日、年、小时等),并输出一个包含所提供范围的日期范围表。

查询的原因是因为我要为报表服务器写很多报表,而且很多时候报表请求要按小时、天、周、月等细分,而我必须不断地编写相同的一遍又一遍地查询以生成日期范围表。我使用这些表左连接,以便我的最终输出不会跳过任何日期范围,以防该范围没有数据,它仍然会填充零并正确绘制图表。

我不认为有任何方法可以使此代码在函数中工作,除非有完全不同的方式来编写它(编辑,请参阅下面的更新)...我敢肯定我可以走很长的路拥有某种长 case 语句的路线,而不是每个增量类型(秒、分钟、小时、日、周、月、季度、年),然后我可以消除动态 SQL。也许我可以使用递归 CTE 并消除 while 循环。但我对这些没有任何经验。

代码如下:

DECLARE @DateFrom DATETIME = '2017-01-01',
        @DateTo DATETIME = '2017-02-02 23:59:59.997',
        @Increment VARCHAR(20) = 'mm'

DECLARE @SQL NVARCHAR(MAX) = NULL

IF OBJECT_ID('tempdb..#DateRange') IS NOT NULL DROP TABLE #DateRange --SELECT * FROM #DateRange
CREATE TABLE #DateRange (BeginDate DATETIME, EndDate DATETIME)

SELECT @SQL = '
    DECLARE @TargetDate DATETIME = ''' + CONVERT(VARCHAR, @DateFrom, 121) + '''
    IF DATEDIFF('+ @Increment +', ''' + CONVERT(VARCHAR, @DateFrom, 121) + ''', ''' + CONVERT(VARCHAR, @DateTo, 121) + ''') > 2000 RETURN

    WHILE (1=1)
    BEGIN
        INSERT INTO #DateRange
        SELECT BeginDate = DATEADD('+ @Increment +', DATEDIFF('+ @Increment +', 0, @TargetDate)    , 0)
            , EndDate =    DATEADD(ms, -3, DATEADD('+ @Increment +', DATEDIFF('+ @Increment +', 0, @TargetDate) + 1, 0))

        SET @TargetDate = DATEADD('+ @Increment +', 1, @TargetDate)
        IF @TargetDate > ''' + CONVERT(VARCHAR, @DateTo, 121) + ''' BREAK
    END'

EXEC sp_executesql @SQL

SELECT * FROM #DateRange

编辑:这是一个修改版本,不确定这是否会被认为“更好”,但至少它消除了动态 SQL,并且我可以将它用作函数。

EDIT2:由于运行速度更快,我将限制更改为返回 5000 条记录。我还改变了它的运行方向,所以它从 DateTo 开始并向后工作。这样,当它达到极限时,它只会停止返回到目前为止。我还添加了一些安全性,例如检查 Increment (不确定还有什么可以调用它?)选项值。我讨厌那个巨大的案例陈述,但我看不出有任何其他方法可以做到这一点。

CREATE FUNCTION dbo.uf_DateRange (  
    @DateFrom DATETIME,
    @DateTo DATETIME,
    @Increment VARCHAR(20)
)
RETURNS @Return TABLE  (
    BeginDate       DATETIME,
    EndDate         DATETIME
)
AS
BEGIN
    IF @Increment NOT IN ('year','yy','yyyy','quarter','qq','q','month','mm','m','week','wk','ww','day','dd','d','hour','hh','minute','mi','n','second','ss','s') RETURN

    DECLARE @TargetDate DATETIME = @DateTo,
            @LoopLimit INT = 5000,
            @Counter INT = 0

    DECLARE @DateRange TABLE (BeginDate DATETIME, EndDate DATETIME)

    WHILE (@Counter < @LoopLimit)
    BEGIN
        INSERT INTO @Return (BeginDate, EndDate)
        SELECT BeginDate    =   CASE WHEN @Increment IN ('year'   , 'yy', 'yyyy') THEN DATEADD(yy, DATEDIFF(yy, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('quarter', 'qq', 'q'   ) THEN DATEADD(qq, DATEDIFF(qq, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('month'  , 'mm', 'm'   ) THEN DATEADD(mm, DATEDIFF(mm, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('week'   , 'wk', 'ww'  ) THEN DATEADD(ww, DATEDIFF(ww, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('day'    , 'dd', 'd'   ) THEN DATEADD(dd, DATEDIFF(dd, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('hour'   , 'hh'        ) THEN DATEADD(hh, DATEDIFF(hh, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('minute' , 'mi', 'n'   ) THEN DATEADD(mi, DATEDIFF(mi, 0, @TargetDate), 0)
                                     WHEN @Increment IN ('second' , 'ss', 's'   ) THEN DATEADD(ss, DATEDIFF(ss, 0, @TargetDate), 0)
                                END
            , EndDate       =   DATEADD(ms, -3,
                                CASE WHEN @Increment IN ('year'   , 'yy', 'yyyy') THEN DATEADD(yy, DATEDIFF(yy, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('quarter', 'qq', 'q'   ) THEN DATEADD(qq, DATEDIFF(qq, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('month'  , 'mm', 'm'   ) THEN DATEADD(mm, DATEDIFF(mm, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('week'   , 'wk', 'ww'  ) THEN DATEADD(ww, DATEDIFF(ww, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('day'    , 'dd', 'd'   ) THEN DATEADD(dd, DATEDIFF(dd, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('hour'   , 'hh'        ) THEN DATEADD(hh, DATEDIFF(hh, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('minute' , 'mi', 'n'   ) THEN DATEADD(mi, DATEDIFF(mi, 0, @TargetDate) + 1, 0)
                                     WHEN @Increment IN ('second' , 'ss', 's'   ) THEN DATEADD(ss, DATEDIFF(ss, 0, @TargetDate) + 1, 0)
                                END)        
        SET @TargetDate     =   CASE WHEN @Increment IN ('year'   , 'yy', 'yyyy') THEN DATEADD(yy, -1, @TargetDate)
                                     WHEN @Increment IN ('quarter', 'qq', 'q'   ) THEN DATEADD(qq, -1, @TargetDate)
                                     WHEN @Increment IN ('month'  , 'mm', 'm'   ) THEN DATEADD(mm, -1, @TargetDate)
                                     WHEN @Increment IN ('week'   , 'wk', 'ww'  ) THEN DATEADD(ww, -1, @TargetDate)
                                     WHEN @Increment IN ('day'    , 'dd', 'd'   ) THEN DATEADD(dd, -1, @TargetDate)
                                     WHEN @Increment IN ('hour'   , 'hh'        ) THEN DATEADD(hh, -1, @TargetDate)
                                     WHEN @Increment IN ('minute' , 'mi', 'n'   ) THEN DATEADD(mi, -1, @TargetDate)
                                     WHEN @Increment IN ('second' , 'ss', 's'   ) THEN DATEADD(ss, -1, @TargetDate)
                                END
        IF @TargetDate > @DateTo BREAK
        SET @Counter += 1
    END

    RETURN
END
GO

【问题讨论】:

    标签: sql function tsql dynamic


    【解决方案1】:

    您将在这里遇到很多限制。我的理解是一个函数不能执行一个存储过程,所以这是一个很大的阻碍。此外,您不能使用函数来执行 INSERT(或 UPDATE 或 DELETE),所以还有另一个大问题。

    您是否有特定原因不能将其保留为程序?

    【讨论】:

    • 我能够通过使用丑陋的 case 语句来消除动态 sql。请参阅我对原始帖子的更新。当代码的目的是输出要在另一个查询中使用的数据时,我不喜欢使用存储过程。在我的大脑中,我将表值函数视为一种参数化视图。当您想要从 proc 输出数据时,您需要知道 proc 的整个输出以及所有数据类型,然后为 proc 创建一个表以将数据输出到其中。使用函数,您只需 SELECT * FROM [Function],就像使用表格一样。更容易使用。
    • 您明确不能在 UDF 中执行 INSERT,因此这对您不起作用。在这种情况下,表类型可能会起作用,这与 TEMP 表非常相似。
    • 是的,我知道它不会起作用,但是看看我最新的更新,尽管它很丑,但它确实有效,而且我一直在使用它。您不能对表进行插入,但可以对表变量进行插入。
    猜你喜欢
    • 2019-10-11
    • 2018-02-12
    • 2018-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-27
    • 2022-01-15
    相关资源
    最近更新 更多