SQL: 有没有办法迭代声明一个变量?
SQL: Is there a way to iteratively DECLARE a variable?
有没有办法在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 date
和 end date
(除了另一个参数)。这将生成日期范围之间的数据。但是,用户还想检查集合 2013 -> 当前年份中所有其他年份的相同日期范围。
棘手的部分是:用户可以输入 2013 年到当前年份之间 任何 年的日期范围,我需要 return 输入数据年以及其他年份的数据。例如,如果用户输入 2014 年 1 月 1 日 - 2014 年 6 月 1 日,那么我需要 return 相同的范围,但年份为 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
如果有更好的方法,我会洗耳恭听。
这样的事情对你有用吗?它使用动态 SQL 来构建查询。我不知道您的日期格式,但为了以防万一,我使用 cast 将时间部分从标准 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
我使用 UDF 创建动态日期范围。
例如
Select DateR1=RetVal,DateR2=DateAdd(MM,5,RetVal) from [dbo].[udf-Create-Range-Date]('2013-01-01','2016-01-01','YY',1)
Returns
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)
精简版 - 非 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
仅获取过去 4 年的 start/end 范围:
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
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
每当您希望生成数字不同(递增等)的事物列表时,请考虑使用 Tally table(数字 table)。生成日期是统计的一个很好的应用 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
在查询中使用计数 table 进行计算通常比循环、游标或动态 SQL 更有效。在这种情况下,与提供的其他答案相比,我认为它也更容易编程和维护。
看到其他几个答案后,我必须说,我强烈建议您不要创建大量带编号的变量来保存这些值。在您可能使用数组或列表或其他一些数据结构的其他语言中,这通常是糟糕的风格,更不用说 SQL,其中 集合以及操作和存储它们的方法是该语言的基础本身.
也许我没有看到你的特定用例,但即使你使用代码创建这些编号变量,你也必须编写 更多 代码来实际调用这些任何后续逻辑或计算中的变量。
首先,像这样创建一个数字 table。
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 日修复
提到的其他方法对于您尝试做的事情要好得多,但就回答问题 "Is there a way to iteratively DECLARE a variable?" 而言,您可以使用动态 SQL 做一些类似于我下面的事情。基本上,您将使用循环创建一个字符串,其中包含您的声明语句。然后你会创建一个额外的字符串(或字符串)来使用它们。在此示例中,我只是创建变量并将它们设置为今天的日期。然后我select每个变量。
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中完成这样的事情:
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 date
和 end date
(除了另一个参数)。这将生成日期范围之间的数据。但是,用户还想检查集合 2013 -> 当前年份中所有其他年份的相同日期范围。
棘手的部分是:用户可以输入 2013 年到当前年份之间 任何 年的日期范围,我需要 return 输入数据年以及其他年份的数据。例如,如果用户输入 2014 年 1 月 1 日 - 2014 年 6 月 1 日,那么我需要 return 相同的范围,但年份为 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
如果有更好的方法,我会洗耳恭听。
这样的事情对你有用吗?它使用动态 SQL 来构建查询。我不知道您的日期格式,但为了以防万一,我使用 cast 将时间部分从标准 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
我使用 UDF 创建动态日期范围。
例如
Select DateR1=RetVal,DateR2=DateAdd(MM,5,RetVal) from [dbo].[udf-Create-Range-Date]('2013-01-01','2016-01-01','YY',1)
Returns
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)
精简版 - 非 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
仅获取过去 4 年的 start/end 范围:
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
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
每当您希望生成数字不同(递增等)的事物列表时,请考虑使用 Tally table(数字 table)。生成日期是统计的一个很好的应用 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
在查询中使用计数 table 进行计算通常比循环、游标或动态 SQL 更有效。在这种情况下,与提供的其他答案相比,我认为它也更容易编程和维护。
看到其他几个答案后,我必须说,我强烈建议您不要创建大量带编号的变量来保存这些值。在您可能使用数组或列表或其他一些数据结构的其他语言中,这通常是糟糕的风格,更不用说 SQL,其中 集合以及操作和存储它们的方法是该语言的基础本身.
也许我没有看到你的特定用例,但即使你使用代码创建这些编号变量,你也必须编写 更多 代码来实际调用这些任何后续逻辑或计算中的变量。
首先,像这样创建一个数字 table。
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 日修复
提到的其他方法对于您尝试做的事情要好得多,但就回答问题 "Is there a way to iteratively DECLARE a variable?" 而言,您可以使用动态 SQL 做一些类似于我下面的事情。基本上,您将使用循环创建一个字符串,其中包含您的声明语句。然后你会创建一个额外的字符串(或字符串)来使用它们。在此示例中,我只是创建变量并将它们设置为今天的日期。然后我select每个变量。
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)