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

Dan*_*son 69 sql-server date

我的问题类似于这个 MySQL问题,但是用于SQL Server:

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

SELECT ExplodeDates('2010-01-01', '2010-01-13');
Run Code Online (Sandbox Code Playgroud)

这将返回一个包含以下值的列表:

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
Run Code Online (Sandbox Code Playgroud)

我认为日历/数字表可能能够帮助我.


更新

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

越低越好

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

更新2

阿丽维亚的答案更为简洁.我已经改变了接受的答案.

小智 101

这几行是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)
Run Code Online (Sandbox Code Playgroud)

  • OP 从最正确的答案更改为 Alivia 的答案,因为它更“简洁”。确实很糟糕,因为它的 CPU 密集程度也更高,与普通 WHILE 循环一样慢,比事务性 WHILE 循环慢,并且使用的逻辑 I/O 是普通 WHILE 循环的 8 倍。我强烈建议避免这种方法(增加递归 CTE 或 rCTE)。Rob Farely 的答案是 iTVF(内联表值函数),使其比 rCTE 方法更容易使用。 (2认同)

Rob*_*ley 67

尝试这样的事情:

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
);
Run Code Online (Sandbox Code Playgroud)

然后你使用:

SELECT *
FROM dbo.ExplodeDates('20090401','20090531') as d;
Run Code Online (Sandbox Code Playgroud)

编辑(接受后):

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

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
);
Run Code Online (Sandbox Code Playgroud)

您可以使用以下方法创建这样的表:

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如果我可以不止一次地投票,那么它的表现是惊人的.我测试了一个简单的版本,其中nums是一个数字表,在数字上有一个聚簇索引.如果日期差异为2天,则CTE将聚集指数超过2倍(28%对72%),但如果日期差异为37年,则表格的CTE版本为3%对97%!我希望我知道为什么这么快...... (3认同)

小智 17

这完全是你想要的,修改自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;
Run Code Online (Sandbox Code Playgroud)

  • 我在更复杂的日期集上收到以下错误:`语句终止。在语句完成之前,最大递归 100 已用尽。`因此,我应该向希望在大范围内使用此答案的其他人指出,您需要添加一个 maxrecursion 值 - `OPTION (MAXRECURSION 0)`。 (2认同)

小智 6

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;
Run Code Online (Sandbox Code Playgroud)