在TSQL中生成递增date的结果集

考虑需要创builddate的结果集。 我们有开始date和结束date,我们希望在中间生成一个date列表。

DECLARE @Start datetime ,@End datetime DECLARE @AllDates table (@Date datetime) SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009' --need to fill @AllDates. Trying to avoid looping. -- Surely if a better solution exists. 

考虑使用WHILE循环的当前实现:

 DECLARE @dCounter datetime SELECT @dCounter = @Start WHILE @dCounter <= @End BEGIN INSERT INTO @AllDates VALUES (@dCounter) SELECT @dCounter=@dCounter+1 END 

问题:如何使用T-SQL创build一组在用户定义的范围内的date? 假设SQL 2005+。 如果您的答案是使用SQL 2008function,请标记为这样。

如果你的date不超过2047天:

 declare @dt datetime, @dtEnd datetime set @dt = getdate() set @dtEnd = dateadd(day, 100, @dt) select dateadd(day, number, @dt) from (select distinct number from master.dbo.spt_values where name is null ) n where dateadd(day, number, @dt) < @dtEnd 

以下使用recursionCTE(SQL Server 2005+):

 WITH dates AS ( SELECT CAST('2009-01-01' AS DATETIME) 'date' UNION ALL SELECT DATEADD(dd, 1, t.date) FROM dates t WHERE DATEADD(dd, 1, t.date) <= '2009-02-01') SELECT ... FROM TABLE t JOIN dates d ON d.date = t.date --etc. 

为了使这个方法起作用,你需要做一次表设置:

 SELECT TOP 10000 IDENTITY(int,1,1) AS Number INTO Numbers FROM sys.objects s1 CROSS JOIN sys.objects s2 ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number) 

Numbers表设置完成后,使用以下查询:

 SELECT @Start+Number-1 FROM Numbers WHERE Number<=DATEDIFF(day,@Start,@End)+1 

捕捉他们呢:

 DECLARE @Start datetime ,@End datetime DECLARE @AllDates table (Date datetime) SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009' INSERT INTO @AllDates (Date) SELECT @Start+Number-1 FROM Numbers WHERE Number<=DATEDIFF(day,@Start,@End)+1 SELECT * FROM @AllDates 

输出:

 Date ----------------------- 2009-03-01 00:00:00.000 2009-03-02 00:00:00.000 2009-03-03 00:00:00.000 2009-03-04 00:00:00.000 2009-03-05 00:00:00.000 2009-03-06 00:00:00.000 2009-03-07 00:00:00.000 2009-03-08 00:00:00.000 2009-03-09 00:00:00.000 2009-03-10 00:00:00.000 .... 2009-07-25 00:00:00.000 2009-07-26 00:00:00.000 2009-07-27 00:00:00.000 2009-07-28 00:00:00.000 2009-07-29 00:00:00.000 2009-07-30 00:00:00.000 2009-07-31 00:00:00.000 2009-08-01 00:00:00.000 (154 row(s) affected) 

@ KM的答案首先创build一个数字表,并用它来select一个date范围。 如果没有临时号码表,请执行以下操作:

 DECLARE @Start datetime ,@End datetime DECLARE @AllDates table (Date datetime) SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009'; WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ), Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ), Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ), Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ), Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 ) SELECT @Start+n-1 as Date FROM ( SELECT ROW_NUMBER() OVER (ORDER BY n) FROM Nbrs ) D ( n ) WHERE n <= DATEDIFF(day,@Start,@End)+1 ; 

testing当然,如果你经常这样做,永久性表格可能会更好的performance。

上面的查询是本文的一个修改版本,它讨论了生成序列并给出了许多可能的方法。 我喜欢这个,因为它不会创build临时表,并且不限于sys.objects表中元素的sys.objects

尝试这个。 没有循环,CTE的限制等,你可以有几乎没有。 生成的logging。 根据需要pipe理交叉连接和顶层。

 select top 100000 dateadd(d,incr,'2010-04-01') as dt from (select incr = row_number() over (order by object_id, column_id), * from ( select a.object_id, a.column_id from sys.all_columns a cross join sys.all_columns b ) as a ) as b 

请注意嵌套是为了更容易控制和转换为视图等。

另一种select是在.NET中创build相应的函数。 以下是它的样子:

 [Microsoft.SqlServer.Server.SqlFunction( DataAccess = DataAccessKind.None, FillRowMethodName = "fnUtlGetDateRangeInTable_FillRow", IsDeterministic = true, IsPrecise = true, SystemDataAccess = SystemDataAccessKind.None, TableDefinition = "d datetime")] public static IEnumerable fnUtlGetDateRangeInTable(SqlDateTime startDate, SqlDateTime endDate) { // Check if arguments are valid int numdays = Math.Min(endDate.Value.Subtract(startDate.Value).Days,366); List<DateTime> res = new List<DateTime>(); for (int i = 0; i <= numdays; i++) res.Add(dtStart.Value.AddDays(i)); return res; } public static void fnUtlGetDateRangeInTable_FillRow(Object row, out SqlDateTime d) { d = (DateTime)row; } 

这基本上是一个原型,它可以做得更聪明,但说明了这个想法。 从我的经验来看,对于一个小到中等的时间跨度来说(比如几年),这个函数比在T-SQL中实现的更好。 CLR版本的另一个不错的function是它不会创build临时表。

概观

这是我的版本(2005兼容)。 这种方法的优点是:

  • 你会得到一个通用的function,你可以使用一些类似的场景; 不限于date
  • 范围不受现有表格的内容的限制
  • 您可以轻松更改增量(例如,每7天获取date而不是每天)
  • 你不需要访问其他目录(即主)
  • SQL引擎能够做一些优化的TVF,它不能与一个while语句
  • generate_series被用在其他一些dbs中,所以这可能有助于让你的代码本能地为更多的用户所熟悉

SQL小提琴: http ://sqlfiddle.com/#!6/c3896/1

基于给定参数生成一系列数字的可重用函数:

 create function dbo.generate_series ( @start bigint , @stop bigint , @step bigint = 1 , @maxResults bigint = 0 --0=unlimitted ) returns @results table(n bigint) as begin --avoid infinite loop (ie where we're stepping away from stop instead of towards it) if @step = 0 return if @start > @stop and @step > 0 return if @start < @stop and @step < 0 return --ensure we don't overshoot set @stop = @stop - @step --treat negatives as unlimited set @maxResults = case when @maxResults < 0 then 0 else @maxResults end --generate output ;with myCTE (n,i) as ( --start at the beginning select @start , 1 union all --increment in steps select n + @step , i + 1 from myCTE --ensure we've not overshot (accounting for direction of step) where (@maxResults=0 or i<@maxResults) and ( (@step > 0 and n <= @stop) or (@step < 0 and n >= @stop) ) ) insert @results select n from myCTE option (maxrecursion 0) --sadly we can't use a variable for this; however checks above should mean that we have a finite number of recursions / @maxResults gives users the ability to manually limit this --all good return end 

把它用于你的场景:

 declare @start datetime = '2013-12-05 09:00' ,@end datetime = '2014-03-02 13:00' --get dates (midnight) --, rounding <12:00 down to 00:00 same day, >=12:00 to 00:00 next day --, incrementing by 1 day select CAST(n as datetime) from dbo.generate_series(cast(@start as bigint), cast(@end as bigint), default, default) --get dates (start time) --, incrementing by 1 day select CAST(n/24.0 as datetime) from dbo.generate_series(cast(@start as float)*24, cast(@end as float)*24, 24, default) --get dates (start time) --, incrementing by 1 hour select CAST(n/24.0 as datetime) from dbo.generate_series(cast(@start as float)*24, cast(@end as float)*24, default, default) 

2005兼容

  • 公用表expression式: http : //technet.microsoft.com/en-us/library/ms190766(v=sql.90).aspx
  • 选项MaxRecursion提示: http ://technet.microsoft.com/en-us/library/ms181714(v=sql.90) .aspx
  • 表值函数: http : //technet.microsoft.com/en-us/library/ms191165(v=sql.90).aspx
  • 默认参数: http : //technet.microsoft.com/en-us/library/ms186755(v=sql.90).aspx
  • date时间: http : //technet.microsoft.com/en-us/library/ms187819( v= sql.90).aspx
  • 投射: http : //technet.microsoft.com/en-us/library/aa226054(v=sql.90).aspx

使用从0到两个date之间的差异的整数创build一个临时表。

 SELECT DATE_ADD(@Start, INTERVAL tmp_int DAY) AS the_date FROM int_table; 

我使用以下内容:

 SELECT * FROM dbo.RangeDate(GETDATE(), DATEADD(d, 365, GETDATE())); -- Generate a range of up to 65,536 contiguous DATES CREATE FUNCTION dbo.RangeDate ( @date1 DATE = NULL , @date2 DATE = NULL ) RETURNS TABLE AS RETURN ( SELECT D = DATEADD(d, AN, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END) FROM dbo.RangeSmallInt(0, ABS(DATEDIFF(d, @date1, @date2))) A ); -- Generate a range of up to 65,536 contiguous BIGINTS CREATE FUNCTION dbo.RangeSmallInt ( @num1 BIGINT = NULL , @num2 BIGINT = NULL ) RETURNS TABLE AS RETURN ( WITH Numbers(N) AS ( SELECT N FROM(VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240 , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256 ) V (N) ) SELECT TOP ( CASE WHEN @num1 IS NOT NULL AND @num2 IS NOT NULL THEN ABS(@num1 - @num2) + 1 ELSE 0 END ) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1 FROM Numbers A , Numbers B WHERE ABS(@num1 - @num2) + 1 < 65537 ); 

这与已经提出的许多解决scheme并不完全相同,但是我喜欢它的几个方面:

  • 没有要求的表格
  • 参数可以以任何顺序传递
  • 限制65,536个date是任意的,并且可以通过交换到一个函数(如RangeInt)来轻松扩展

这个解决scheme基于对MySQL相同问题的奇妙回答。 在MSSQL上也是非常高效的。 https://stackoverflow.com/a/2157776/466677

 select DateGenerator.DateValue from ( select DATEADD(day, - (aa + (10 * ba) + (100 * ca) + (1000 * da)), CONVERT(DATE, GETDATE()) ) as DateValue from (select aa from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as a(a)) as a cross join (select ba from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as b(a)) as b cross join (select ca from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as c(a)) as c cross join (select da from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as d(a)) as d ) DateGenerator WHERE DateGenerator.DateValue BETWEEN 'Mar 1 2009' AND 'Aug 1 2009' ORDER BY DateGenerator.DateValue ASC 

仅适用于过去的date,未来更改中的date减去DATEADD函数中的date。 查询只适用于SQL Server 2008+,但也可以通过将“从值select”构造replace为工会来重写。

我build议:创build一个数字辅助表,并使用它来生成date列表。 你也可以使用一个recursion的CTE,但是这可能不如join数字的辅助表格那样好。 有关这两个选项的信息,请参阅SQL,数字辅助表 。

虽然我真的很喜欢KM的解决scheme(+1),但是我必须质疑你的“无循环”的假设 – 考虑到你的应用程序可能的date范围,有一个循环不应该是很昂贵的。 主要的技巧是在staging / cache表中对循环的结果进行strore处理,这样,通过重新计算相同的确切date,极大的查询集合不会使系统变慢。 例如,每个查询只计算/caching尚不在caching中的date范围,并且需要(并预先填充一些实际的date范围,如约2年前的范围,范围由您的应用程序业务需求决定)。

最好的答案可能是使用CTE,但不能保证你能够使用它。 在我的情况下,我不得不将这个列表插入由查询生成器dinamically创build的现有查询…不能使用CTE或存储过程。

所以,Devio的回答是非常有用的,但我必须修改它才能在我的环境中工作。

如果您无法访问主数据库,则可以在数据库中使用另一个表。 就以前的例子而言,最大date范围由表格中select的行数给出。

在我的例子中,使用row_number,可以使用没有实际int列的表。

 declare @bd datetime --begin date declare @ed datetime --end date set @bd = GETDATE()-50 set @ed = GETDATE()+5 select DATEADD(dd, 0, DATEDIFF(dd, 0, Data)) --date format without time from ( select (GETDATE()- DATEDIFF(dd,@bd,GETDATE())) --Filter on the begin date -1 + ROW_NUMBER() over (ORDER BY [here_a_field]) AS Data from [Table_With_Lot_Of_Rows] ) a where Data < (@ed + 1) --filter on the end date 

真的很喜欢Devio的解决scheme,因为我需要这样的东西,需要在SQL Server 2000上运行(所以不能使用CTE),但是如何修改它以仅生成与给定的一周中的datealignment的date。 例如,我只希望与周一,周三和周五一致的date或者我select的任何特定顺序基于以下数字Scheme:

 Sunday = 1 Monday = 2 Tuesday = 3 Wednesday = 4 Thursday = 5 Friday = 6 Saturday = 7 

例:

 StartDate = '2015-04-22' EndDate = '2017-04-22' --2 years worth Filter on: 2,4,6 --Monday, Wednesday, Friday dates only 

我想要编码是添加两个额外的字段:day,day_code然后筛选条件生成的列表…

我想出了以下几点:

 declare @dt datetime, @dtEnd datetime set @dt = getdate() set @dtEnd = dateadd(day, 1095, @dt) select dateadd(day, number, @dt) as Date, DATENAME(DW, dateadd(day, number, @dt)) as Day_Name into #generated_dates from (select distinct number from master.dbo.spt_values where name is null ) n where dateadd(day, number, @dt) < @dtEnd select * from #generated_dates where Day_Name in ('Saturday', 'Friday') drop table #generated_dates 

我喜欢CTE,因为它很容易阅读和维护

 Declare @mod_date_from date =getdate(); Declare @mod_date_to date =dateadd(year,1,@mod_date_from); with cte_Dates as ( SELECT @mod_date_from as reqDate UNION ALL SELECT DATEADD(DAY,1,reqDate) FROM cte_Dates WHERE DATEADD(DAY,1,reqDate) < @mod_date_to ) SELECT * FROM cte_Dates OPTION(MAXRECURSION 0); 

不要忘记设置MAXRECURSION

这一个应该工作。

从sysobjects中selectTop 1000 DATEADD(d,ROW_NUMBER()OVER(ORDER BY ID),getdate())