使用函数获取两个日期之间的日期列表

我的问题类似于这个 MySQL的问题,但旨在为SQL Server:

是否有一个函数或查询将返回两个日期之间的天数列表? 例如,假设有一个名为ExplodeDates的函数:

SELECT ExplodeDates('2010-01-01', '2010-01-13'); 

这将返回单个列表的值:

 2010-01-01 2010-01-02 2010-01-03 2010-01-04 2010-01-05 2010-01-06 2010-01-07 2010-01-08 2010-01-09 2010-01-10 2010-01-11 2010-01-12 2010-01-13 

我在想一个日历/数字表格可以帮助我。


更新

我决定看看提供的三个代码答案,执行的结果(占总批次的百分比)是:

  • Rob Farley的回答 :18%
  • StingyJack的回答 :41%
  • KM的回答 :41%

越低越好

尽管数字表格解决方案(KM和StingyJack在他们的答案中使用)是我最喜欢的,但我接受了Rob Farley的答案,因为它是最快的。 Rob Farley的速度提高了三分之二。

更新2

Alivia的回答更为简洁。 我改变了接受的答案。

尝试这样的事情:

 CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime) returns table as return ( with N0 as (SELECT 1 as n UNION ALL SELECT 1) ,N1 as (SELECT 1 as n FROM N0 t1, N0 t2) ,N2 as (SELECT 1 as n FROM N1 t1, N1 t2) ,N3 as (SELECT 1 as n FROM N2 t1, N2 t2) ,N4 as (SELECT 1 as n FROM N3 t1, N3 t2) ,N5 as (SELECT 1 as n FROM N4 t1, N4 t2) ,N6 as (SELECT 1 as n FROM N5 t1, N5 t2) ,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6) SELECT DATEADD(day,num-1,@startdate) as thedate FROM nums WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1 ); 

然后你使用:

 SELECT * FROM dbo.ExplodeDates('20090401','20090531') as d; 

编辑(接受后):

请注意…如果您已经有足够大的数字表,那么您应该使用:

 CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime) returns table as return ( SELECT DATEADD(day,num-1,@startdate) as thedate FROM nums WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1 ); 

你可以用下面的方法创建这样的表格:

 CREATE TABLE dbo.nums (num int PRIMARY KEY); INSERT dbo.nums values (1); GO INSERT dbo.nums SELECT num + (SELECT COUNT(*) FROM nums) FROM nums GO 20 

这些行将创建一个包含1M行的数字表…并且比逐一插入它们要快得多。

您不应使用涉及BEGIN和END的函数来创建ExplodeDates函数,因为查询优化器根本无法简化查询。

这几行是在sql server中这个问题的简单答案。

 WITH mycte AS ( SELECT CAST('2011-01-01' AS DATETIME) DateValue UNION ALL SELECT DateValue + 1 FROM mycte WHERE DateValue + 1 < '2021-12-31' ) SELECT DateValue FROM mycte OPTION (MAXRECURSION 0) 

这正是你想要的,从Will的早期文章中修改。 无需辅助表或循环。

 WITH date_range (calc_date) AS ( SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '2010-01-13') - DATEDIFF(DAY, '2010-01-01', '2010-01-13'), 0) UNION ALL SELECT DATEADD(DAY, 1, calc_date) FROM date_range WHERE DATEADD(DAY, 1, calc_date) <= '2010-01-13') SELECT calc_date FROM date_range; 

我是一个oracle的家伙,但我相信MS SQL Server支持connect by子句:

 select sysdate + level from dual connect by level <= 10 ; 

输出是:

 SYSDATE+LEVEL 05-SEP-09 06-SEP-09 07-SEP-09 08-SEP-09 09-SEP-09 10-SEP-09 11-SEP-09 12-SEP-09 13-SEP-09 14-SEP-09 

Dual只是一个与Oracle相关的“虚拟”表(它包含1行,单词“dummy”作为单列的值)。

 DECLARE @MinDate DATETIME = '2012-09-23 00:02:00.000', @MaxDate DATETIME = '2012-09-25 00:00:00.000'; SELECT TOP (DATEDIFF(DAY, @MinDate, @MaxDate) + 1) Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @MinDate) FROM sys.all_objects a CROSS JOIN sys.all_objects b; 

一些想法:

如果你需要列表日期以循环它们,你可以有一个开始日期和日期计数参数,并做一个while循环,同时创建日期和使用它?

使用C#CLR存储过程并将代码写入C#

在代码之外的数据库中执行此操作

所有这些日期都已经在数据库中了吗?还是只是想知道两个日期之间的日子? 如果是第一个,则可以使用BETWEEN<=> =查找之间的日期

例:

 SELECT column_name(s) FROM table_name WHERE column_name BETWEEN value1 AND value2 

要么

 SELECT column_name(s) FROM table_name WHERE column_name value1 >= column_name AND column_name =< value2 

您只需更改下面提供的代码中的硬编码值即可

 DECLARE @firstDate datetime DECLARE @secondDate datetime DECLARE @totalDays INT SELECT @firstDate = getDate() - 30 SELECT @secondDate = getDate() DECLARE @index INT SELECT @index = 0 SELECT @totalDays = datediff(day, @firstDate, @secondDate) CREATE TABLE #temp ( ID INT NOT NULL IDENTITY(1,1) ,CommonDate DATETIME NULL ) WHILE @index < @totalDays BEGIN INSERT INTO #temp (CommonDate) VALUES (DATEADD(Day, @index, @firstDate)) SELECT @index = @index + 1 END SELECT CONVERT(VARCHAR(10), CommonDate, 102) as [Date Between] FROM #temp DROP TABLE #temp 

在你使用我的函数之前,你需要设置一个“助手”表,每个数据库只需要这样做一次:

 CREATE TABLE Numbers (Number int NOT NULL, CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] DECLARE @x int SET @x=0 WHILE @x<8000 BEGIN SET @x=@x+1 INSERT INTO Numbers VALUES (@x) END 

这里是功能:

 CREATE FUNCTION dbo.ListDates ( @StartDate char(10) ,@EndDate char(10) ) RETURNS @DateList table ( Date datetime ) AS BEGIN IF ISDATE(@StartDate)!=1 OR ISDATE(@EndDate)!=1 BEGIN RETURN END INSERT INTO @DateList (Date) SELECT CONVERT(datetime,@StartDate)+n.Number-1 FROM Numbers n WHERE Number<=DATEDIFF(day,@StartDate,CONVERT(datetime,@EndDate)+1) RETURN END --Function 

用这个:

 select * from dbo.ListDates('2010-01-01', '2010-01-13') 

输出:

 Date ----------------------- 2010-01-01 00:00:00.000 2010-01-02 00:00:00.000 2010-01-03 00:00:00.000 2010-01-04 00:00:00.000 2010-01-05 00:00:00.000 2010-01-06 00:00:00.000 2010-01-07 00:00:00.000 2010-01-08 00:00:00.000 2010-01-09 00:00:00.000 2010-01-10 00:00:00.000 2010-01-11 00:00:00.000 2010-01-12 00:00:00.000 2010-01-13 00:00:00.000 (13 row(s) affected) 

也许如果你想要一个更简单的方法,这应该做到这一点。

 WITH date_range (calc_date) AS ( SELECT DATEADD(DAY, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP) - 6, 0) UNION ALL SELECT DATEADD(DAY, 1, calc_date) FROM date_range WHERE DATEADD(DAY, 1, calc_date) < CURRENT_TIMESTAMP) SELECT calc_date FROM date_range; 

但临时表也是一个非常好的方法。 也许你也应该考虑一个人口日历表。

肯定是一个数字表,尽管如果你真的需要这个表现的话,你可能会想用马克·雷德曼的CLR过程/汇编的思想。

如何创建日期表(以及创建数字表的超快速方式)

 /*Gets a list of integers into a temp table (Jeff Moden's idea from SqlServerCentral.com)*/ SELECT TOP 10950 /*30 years of days*/ IDENTITY(INT,1,1) as N INTO #Numbers FROM Master.dbo.SysColumns sc1, Master.dbo.SysColumns sc2 /*Create the dates table*/ CREATE TABLE [TableOfDates]( [fld_date] [datetime] NOT NULL, CONSTRAINT [PK_TableOfDates] PRIMARY KEY CLUSTERED ( [fld_date] ASC )WITH FILLFACTOR = 99 ON [PRIMARY] ) ON [PRIMARY] /*fill the table with dates*/ DECLARE @daysFromFirstDateInTheTable int DECLARE @firstDateInTheTable DATETIME SET @firstDateInTheTable = '01/01/1998' SET @daysFromFirstDateInTheTable = (SELECT (DATEDIFF(dd, @firstDateInTheTable ,GETDATE()) + 1)) INSERT INTO TableOfDates SELECT DATEADD(dd,nums.n - @daysFromFirstDateInTheTable, CAST(FLOOR(CAST(GETDATE() as FLOAT)) as DateTime)) as FLD_Date FROM #Numbers nums 

现在你已经有了一个日期表,你可以使用一个像KM这样的函数(NOT A PROC)来获取它们的表格。

 CREATE FUNCTION dbo.ListDates ( @StartDate DATETIME ,@EndDate DATETIME ) RETURNS @DateList table ( Date datetime ) AS BEGIN /*add some validation logic of your own to make sure that the inputs are sound.Adjust the rest as needed*/ INSERT INTO @DateList SELECT FLD_Date FROM TableOfDates (NOLOCK) WHERE FLD_Date >= @StartDate AND FLD_Date <= @EndDate RETURN END 

晚了一点,但我喜欢这个解决方案。

 CREATE FUNCTION ExplodeDates(@startDate DateTime, @endDate DateTime) RETURNS table as return ( SELECT TOP (DATEDIFF(DAY, @startDate, @endDate) + 1) DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @startDate) AS DATE FROM sys.all_objects a CROSS JOIN sys.all_objects b ) 
 Declare @date1 date = '2016-01-01' ,@date2 date = '2016-03-31' ,@date_index date Declare @calender table (D date) SET @date_index = @date1 WHILE @date_index<=@date2 BEGIN INSERT INTO @calender SELECT @date_index SET @date_index = dateadd(day,1,@date_index) IF @date_index>@date2 Break ELSE Continue END 

– ###另一半打了六打。 另一种方法假设MsSql

 Declare @MonthStart datetime = convert(DateTime,'07/01/2016') Declare @MonthEnd datetime = convert(DateTime,'07/31/2016') Declare @DayCount_int Int = 0 Declare @WhileCount_int Int = 0 set @DayCount_int = DATEDIFF(DAY, @MonthStart, @MonthEnd) select @WhileCount_int WHILE @WhileCount_int < @DayCount_int + 1 BEGIN print convert(Varchar(24),DateAdd(day,@WhileCount_int,@MonthStart),101) SET @WhileCount_int = @WhileCount_int + 1; END; 

如果您想打印从特定年份开始直到当前日期的年份。 刚刚改变了接受的答案。

 WITH mycte AS ( SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue UNION ALL SELECT DateValue + 1 FROM mycte WHERE DateValue + 1 < = YEAR(GETDATE()) ) SELECT DateValue FROM mycte OPTION (MAXRECURSION 0) 

此查询适用于Microsoft SQL Server。

 select distinct format( cast('2010-01-01' as datetime) + ( av / 10 ), 'yyyy-MM-dd' ) as aDate from ( SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n) ) a where format( cast('2010-01-01' as datetime) + ( av / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime) order by aDate asc; 

现在我们来看看它是如何工作的。

内部查询只返回一个从0到9999的整数列表。它将给我们计算日期的10,000个值的范围。 您可以通过添加十万和十万等的行来获得更多的日期。

 SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n) ) a; 

这部分将字符串转换为日期,并从内部查询中为其添加一个数字。

 cast('2010-01-01' as datetime) + ( av / 10 ) 

然后我们将结果转换成你想要的格式。 这也是列名!

 format( cast('2010-01-01' as datetime) + ( av / 10 ), 'yyyy-MM-dd' ) 

接下来,我们只提取不同的值,并为列名赋予aDate的别名。

 distinct format( cast('2010-01-01' as datetime) + ( av / 10 ), 'yyyy-MM-dd' ) as aDate 

我们使用where子句只在您想要的范围内进行日期筛选。 请注意,我们在此使用列名称,因为SQL Server不接受where子句中的列别名aDate。

 where format( cast('2010-01-01' as datetime) + ( av / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime) 

最后,我们对结果进行排序。

  order by aDate asc; 

如果你在像我这样的情况下, 程序和函数被禁止 ,你的sql用户没有权限插入,因此插入不允许 ,也“ 不允许设置/声明像@c临时变量”,但你想生成一个特定时期的日期列表 ,比如当年要做一些聚合,用这个

 select * from (select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from (select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0, (select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1, (select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2, (select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3, (select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v where gen_date between '2017-01-01' and '2017-12-31' 
 WITH TEMP (DIA, SIGUIENTE_DIA ) AS (SELECT 1, CAST(@FECHAINI AS DATE) FROM DUAL UNION ALL SELECT DIA, DATEADD(DAY, DIA, SIGUIENTE_DIA) FROM TEMP WHERE DIA < DATEDIFF(DAY, @FECHAINI, @FECHAFIN) AND DATEADD(DAY, 1, SIGUIENTE_DIA) <= CAST(@FECHAFIN AS DATE) ) SELECT SIGUIENTE_DIA AS CALENDARIO FROM TEMP ORDER BY SIGUIENTE_DIA 

细节在桌子DUAL,但是如果您交换这张桌子为一张假桌子这工作。

 SELECT dateadd(dd,DAYS,'2013-09-07 00:00:00') DATES INTO #TEMP1 FROM (SELECT TOP 365 colorder - 1 AS DAYS from master..syscolumns WHERE id = -519536829 order by colorder) a WHERE datediff(dd,dateadd(dd,DAYS,'2013-09-07 00:00:00'),'2013-09-13 00:00:00' ) >= 0 AND dateadd(dd,DAYS,'2013-09-07 00:00:00') <= '2013-09-13 00:00:00' SELECT * FROM #TEMP1 

答案是在这里avialbe 如何列出两个日期之间的所有日期

 Create Procedure SelectDates(@fromDate Date, @toDate Date) AS BEGIN SELECT DATEADD(DAY,number,@fromDate) [Date] FROM master..spt_values WHERE type = 'P' AND DATEADD(DAY,number,@fromDate) < @toDate END 
 DECLARE @StartDate DATE = '2017-09-13', @EndDate DATE = '2017-09-16' SELECT date FROM ( SELECT DATE = DATEADD(DAY, rn - 1, @StartDate) FROM ( SELECT TOP (DATEDIFF(DAY, @StartDate, DATEADD(DAY,1,@EndDate))) rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id]) FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2 ORDER BY s1.[object_id] ) AS x ) AS y 

结果:

 2017-09-13 2017-09-14 2017-09-15 2017-09-16