SQL:有没有办法迭代地DECLARE变量?

时间:2016-06-15 18:38:42

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

有没有办法在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年集中所有其他年份的相同日期范围 - &gt;当年。

棘手的部分是:用户可以在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

如果有更好的方法可以做到这一点,我全心全意。

7 个答案:

答案 0 :(得分:3)

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

对于exanple

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

答案 1 :(得分:3)

每当您想要生成数字上不同的内容列表(递增等)时,请考虑使用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以及操作和存储它们的方法是语言本身的基础

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

答案 2 :(得分:2)

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

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日

答案 3 :(得分:1)

这样的事情对你有用吗?它使用动态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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

提到的其他方法对于您正在尝试的方法要好得多,但在回答问题方面&#34;有没有办法迭代地DECLARE变量?&#34;,你可以做类似我的事情下面使用动态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)

答案 6 :(得分:0)

仅获得过去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