【问题标题】:SQL: Is there a way to iteratively DECLARE a variable?SQL:有没有办法迭代地声明一个变量?
【发布时间】:2016-06-15 18:38:41
【问题描述】:

有没有办法在 SQL 中完成这样的事情:

DECLARE @iter = 1

WHILE @iter<11
BEGIN
DECLARE @('newdate'+@iter) DATE = [some expression that generates a value]
SET @iter = @iter + 1
END

最后我会有 10 个变量:

@newdate1
@newdate2
@newdate3
@newdate4
@newdate5
@newdate6
@newdate7
@newdate8
@newdate9
@newdate10

更新:

根据评论,我想我应该说明为什么我想这样做。我正在使用 Report Builder 3.0。我将制作一份报告,其中输入将是 start dateend date(除了一个其他参数)。这将生成日期范围之间的数据。但是,用户还希望在设置的 2013 -> 当前年份中检查所有其他年份的相同日期范围。

棘手的部分是:用户可以在 2013 年和当前年份之间的 任何 年份输入日期范围,我需要返回输入年份的数据以及其他年份的数据。例如,如果用户输入 2014 年 1 月 1 日 - 2014 年 6 月 1 日,那么我需要返回相同的范围,但要返回 2013、2015 和 2016 年。

示例输入:

1/1/2016 - 6/1/2016

报告必须为这些值生成数据:

1/1/2013 - 6/1/2013
1/1/2014 - 6/1/2014
1/1/2015 - 6/1/2015
1/1/2016 - 6/1/2016

如果有更好的方法可以做到这一点,我会全力以赴。

【问题讨论】:

  • (1) 使用您正在使用的数据库(可能是 SQL Server)标记您的问题。 (2) 不。没有办法简单地做到这一点。
  • 您为什么要这样做?可能有更好的方法来做你想做的任何事情。例如,临时表或临时表变量。
  • @Nikki9696 可能有。我将根据总体目标编辑我的问题。
  • 您很可能正在寻找临时表。
  • 您可以只使用变量作为开始日期和结束日期,并使用 DATEADD 逻辑来获取其他年份。在没有 10 个变量的 WHERE 子句中,还有许多其他方法可以做到这一点。

标签: sql sql-server loops variables reportbuilder3.0


【解决方案1】:

我使用 UDF 创建动态日期范围。

例如

Select DateR1=RetVal,DateR2=DateAdd(MM,5,RetVal) from [dbo].[udf-Create-Range-Date]('2013-01-01','2016-01-01','YY',1) 

返回

DateR1      DateR2
2013-01-01  2013-06-01
2014-01-01  2014-06-01
2015-01-01  2015-06-01
2016-01-01  2016-06-01

UDF

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)

Returns 
@ReturnVal Table (RetVal datetime)

As
Begin
    With DateTable As (
        Select DateFrom = @DateFrom
        Union All
        Select Case @DatePart
               When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
               When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
               When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
               When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
               When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
               When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
               When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
               When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
               End
        From DateTable DF
        Where DF.DateFrom < @DateTo
    )

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)

    Return
End

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 

精简版 - NON UDF 这可以注入到你的 SQL 中

Declare @startdate Date ='1/1/2014'   -- user supplied value
Declare @enddate Date = '6/1/2014'    -- user supplied value

Declare @DateFrom Date = cast('2013-'+cast(month(@StartDate) as varchar(10))+'-'+cast(Day(@StartDate) as varchar(10)) as date)
Declare @DateTo Date   = cast(cast(Year(GetDate()) as varchar(10))+'-'+cast(month(@enddate) as varchar(10))+'-'+cast(Day(@enddate) as varchar(10)) as date) 
Declare @Incr int = DateDiff(MM,@startdate,@enddate)  -- made to be dynamic based on the user supplied dates

Declare @DateRange Table (DateR1 date,DateR2 Date)

;with DateTable As (
    Select DateFrom = @DateFrom
    Union All
    Select DateAdd(YY, 1, df.dateFrom)
    From DateTable DF
    Where DF.DateFrom < @DateTo
)
Insert into @DateRange(DateR1,DateR2) Select DateR1=DateFrom,DateR2=DateAdd(MM,@Incr,DateFrom) From DateTable option (maxrecursion 32767)

Select * from @DateRange

【讨论】:

    【解决方案2】:

    当您希望生成数字不同(增量等)的事物列表时,请考虑使用Tally table(数字表)。生成日期是计数表的绝佳应用:

    declare @startDate date = '20160101'
    declare @endDate date = '20160601'
    
    select N
        , dateadd(year, (N - 1) * -1, @startDate) as StartDate
        , dateadd(year, (N - 1) * -1, @endDate) as EndDate
    from tally -- Follow the link above for info on how to create this table
    where N <= 4
    order by N desc
    

    结果:

    N   StartDate   EndDate
    4   2013-01-01  2013-06-01
    3   2014-01-01  2014-06-01
    2   2015-01-01  2015-06-01
    1   2016-01-01  2016-06-01
    

    在查询中使用计数表进行计算通常比循环、游标或动态 SQL 更有效。在这种情况下,与提供的其他答案相比,我想说它也更容易编程和维护。

    在看到其他几个答案后,我必须说我强烈建议您创建大量编号变量来保存这些值。在您可能使用数组或列表或其他数据结构的其他语言中,这通常是一种糟糕的风格,更不用说 SQL,其中 集合以及操作和存储它们的方法是语言本身的基础

    也许我没有看到您的特定用例,但即使您使用代码创建这些编号变量,您也必须编写 更多 代码才能在任何后续逻辑中实际调用这些变量或计算。

    【讨论】:

    • 我收到一个错误:Invalid object name 'tally'.
    • @whatwhatwhat - 在尝试在他的答案中使用代码之前,您是否遵循了 Tim 提供的 Tally Table 的 link 并实际 create one ?
    【解决方案3】:

    首先,创建一个像这样的数字表。

    declare @numbers table(n int)
    insert into @Numbers(N)
    select top 1000 row_number() over(order by t1.number) as N
    from   master..spt_values t1 
           cross join master..spt_values t2
    

    然后像这样创建日期

        declare @iniDate date
        declare @endDate date
        delcare @limitDate date
    
        set @iniDate='20160101'
        set @enddate='20160106'
        set @limitDate ='20130101'
    
        select dateadd(yy,-1*(n-1),@inidate), dateadd(yy,-1*(n-1),@enddate) 
        from @numbers where (n-1)<=datediff(yy, @limitDate, @inidate)
    

    编辑: 新请求后,试试这个:

    declare @iniDate date
    declare @endDate date
    declare @iniLimitDate date
    declare @endLimitDate date
    
    set @iniDate='20140201'
    set @endDate='20140206'
    set @iniLimitDate ='20130101'
    set @endLimitDate ='20161231'    
    
    select datefromparts(year(@iniLimitDate)+n-1,month(@iniDate), day(@iniDate))
        ,datefromparts(year(@iniLimitDate)+n-1,month(@endDate), day(@endDate))
    from @numbers 
    where  year(@iniLimitDate)+n-1<=year(@endlimitdate)
        and year(@iniLimitDate)+n-1<>year(@enddate)
    

    注意:需要在 2 月 29 日修复

    【讨论】:

    • 此答案仅适用于 2016 年。我需要允许用户输入任何年份并获取所有其他年份的日期范围。
    • @whatwhatwhat 用户将根据需要设置 @iniDate@endDate
    • 是的。例如,我用@iniDate = 20140101@enddate = 20140106 尝试了你的代码,它返回1/1/2014 - 1/6/20141/1/2013 - 1/6/2013
    • 缺少 2015 年和 2016 年。
    • @whatwhatwhat 您的请求是从输入日期追溯到 2013 年。您到底需要什么?
    【解决方案4】:

    John 有一个比我这里的简单示例更好的解决方案,但是这个不需要单独的 UDF。万一您没有这些权限或其他权限。

    DECLARE @startDate DATETIME, @endDate DATETIME, @tmpStartDate DATETIME, @tmpEndDate DATETIME 
    SET @startDate = '1/1/2016'
    SET @endDate = '6/1/2016'
    
    
    SET @tmpStartDate = @startDate
    SET @tmpEndDate = @endDate
    DECLARE @dateTbl TABLE (startDate DATETIME, endDate DATETIME)
    
    WHILE (DATEPART(YEAR, @tmpStartDate) >= 2013)
    BEGIN
        INSERT INTO @dateTbl VALUES (@tmpStartDate, @tmpEndDate)
        SET @tmpStartDate = DATEADD(year, -1, @tmpStartDate)
        SET @tmpEndDate = DATEADD(year, -1, @tmpStartDate)
    END
    
    SELECT * FROM @dateTbl
    

    【讨论】:

    • 不花哨,我真的很懒,而且我经常需要日期范围
    • 嗯,你的解决方案很优雅,我喜欢它 =)
    • 这个答案只有在他们输入 2016 年的日期时才有效。我的问题有点棘手,因为用户可以输入任何一年内的日期范围,然后我必须返回所有 other年。
    【解决方案5】:

    这样的东西对你有用吗?它使用动态 SQL 来构建查询。我不知道您的日期格式,但我使用强制转换从标准 getDate() 函数中截断时间部分以防万一。

    DECLARE @iter int = 1
    Declare @SQL VARCHAR(MAX)
    
    WHILE @iter<11
    BEGIN
    SET @SQL = ISNULL(@SQL,'') + ' DECLARE @newdate'+ CAST(@iter AS VARCHAR) + ' DATE = ' + CAST(CAST(GETDATE() AS DATE) AS VARCHAR) + ' ' 
    PRINT (@SQL)
    SET @iter = @iter + 1
    END
    
    SET @SQL = @SQL + ' SELECT * FROM blah'
    EXEC @SQL
    

    【讨论】:

      【解决方案6】:

      提到的其他方法对于您尝试做的事情要好得多,但是在回答“有没有办法迭代地声明变量?”这个问题时,您可以使用动态 SQL 执行下面的操作。基本上,您将使用循环创建一个包含您的声明语句的字符串。然后,您将创建一个额外的字符串(或多个字符串)来使用它们。在此示例中,我只是创建变量并将它们设置为今天的日期。然后我选择每个变量。

      DECLARE @iter INT = 1, @SQL VARCHAR(MAX) = '', @MoreSQL VARCHAR(MAX) = '';
      
      WHILE @iter < 11
      BEGIN
          SET @SQL += 'DECLARE @NewDate' + CAST(@iter AS VARCHAR(2)) + ' DATE = GETDATE() '
      
          SET @iter += 1
      END
      
      SET @iter = 1
      
      WHILE @iter < 11
      BEGIN
          SET @MoreSQL  += 'SELECT @NewDate' + CAST(@iter AS VARCHAR(2)) + ' '
      
          SET @iter += 1
      END
      
      SET @SQL += @MoreSQL
      
      EXEC (@SQL)
      

      【讨论】:

      • 应该提到,在动态 SQL 语句的上下文中创建的变量只能在同一动态 SQL 上下文中使用。
      【解决方案7】:

      仅获取最近 4 年的开始/结束范围:

      DECLARE @START DATETIME, @END DATETIME
      
      SET @START='2016-01-01'
      SET @END='2016-06-01'
      
      DECLARE @DATES TABLE (STARTDATE DATETIME, ENDDATE DATETIME)
      
      INSERT INTO @DATES (STARTDATE, ENDDATE)
      SELECT @START,@END UNION
      SELECT DATEADD(YY,-1,@START),DATEADD(YY,-1,@END) UNION
      SELECT DATEADD(YY,-2,@START),DATEADD(YY,-2,@END) UNION
      SELECT DATEADD(YY,-3,@START),DATEADD(YY,-3,@END)
      
      SELECT * FROM @DATES ORDER BY STARTDATE
      

      要获取两者之间的所有日期:

      DECLARE @START DATETIME, @END DATETIME
      
      SET @START='2016-01-01'
      SET @END='2016-06-01'
      
      DECLARE @CURDATE DATETIME
      DECLARE @DATES TABLE (DATEVAL DATETIME)
      
      SET @CURDATE=@START
      
      WHILE @CURDATE<=@END
      BEGIN
          INSERT INTO @DATES (DATEVAL)
          SELECT @CURDATE UNION
          SELECT DATEADD(YY,-1,@CURDATE) UNION
          SELECT DATEADD(YY,-2,@CURDATE) UNION
          SELECT DATEADD(YY,-3,@CURDATE) 
      
          SET @CURDATE=DATEADD(DD,1,@CURDATE)
      END
      
      SELECT * FROM @DATES ORDER BY DATEVAL
      

      获取从@START 到 2013 年的所有年份...

      DECLARE @START DATETIME, @END DATETIME
      
      SET @START='2016-01-01'
      SET @END='2016-06-01'
      
      DECLARE @DATES TABLE (STARTDATE DATETIME, ENDDATE DATETIME)
      
      DECLARE @CURYEAR INT
      SET @CURYEAR=YEAR(@START)
      
      WHILE @CURYEAR>= 2013 
      BEGIN
          INSERT INTO @DATES (STARTDATE, ENDDATE)
          SELECT @START,@END 
      
          SET @START = DATEADD(YY,-1,@START)
          SET @END = DATEADD(YY,-1,@END)
          SET @CURYEAR=YEAR(@START)
      END
      SELECT * FROM @DATES ORDER BY STARTDATE
      

      【讨论】:

      • 这似乎非常接近我的需要,但我需要允许用户输入 any 年的日期范围并返回该年的数据 + 所有其他年。
      • 很容易对此进行调整,但必须有一个“所有其他年份”的有限范围或确定所述范围的方法。
      • 我添加了第三条语句,这可能是您想要的。
      • 哎呀我从来没有提交过评论。是的,有限范围是 2013 -> 当前年份。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-17
      • 2020-03-22
      • 1970-01-01
      • 2016-09-23
      • 1970-01-01
      • 2011-02-14
      • 2021-02-19
      相关资源
      最近更新 更多