生成在TSQL中递增日期的结果集

p.c*_*ell 53 sql t-sql database sql-server sql-server-2005

考虑需要创建日期的结果集.我们有开始和结束日期,我们想要生成两者之间的日期列表.

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

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

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

问题:如何使用T-SQL创建一组在用户定义范围内的日期?假设SQL 2005+.如果您的答案是使用SQL 2008功能,请标记为.

dev*_*vio 50

如果您的日期不超过2047天:

declare @dt datetime, @dtEnd datetime
set @dt = getdate()
set @dtEnd = dateadd(day, 100, @dt)

select dateadd(day, number, @dt)
from 
    (select number from master.dbo.spt_values
     where [type] = 'P'
    ) n
where dateadd(day, number, @dt) < @dtEnd
Run Code Online (Sandbox Code Playgroud)

在多次提出要求后,我更新了我的答案.为什么?

原始答案包含子查询

 select distinct number from master.dbo.spt_values
     where name is null
Run Code Online (Sandbox Code Playgroud)

这与我在SQL Server 2008,2012和2016上测试它们提供了相同的结果.

但是,当我在查询时尝试分析MSSQL内部的代码时spt_values,我发现这些SELECT语句总是包含该子句WHERE [type]='[magic code]'.

因此我决定虽然查询返回正确的结果,但它会出于错误的原因提供正确的结果:

有可能是SQL Server的未来版本,其定义了不同的[type]值,其还具有NULL作为值[name],的0-2047的范围内,或甚至不连续的,外面在这种情况下,结果将是完全错误的.


OMG*_*ies 42

以下使用递归CTE(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.
Run Code Online (Sandbox Code Playgroud)

  • 如果您尝试使用此操作超过100天,则会收到错误"语句已终止.在语句完成之前,最大递归100已用尽." (4认同)

Cha*_*ick 6

@KM 的回答首先创建一个数字表,并使用它来选择日期范围。要在没有临时数字表的情况下执行相同操作:

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

当然,测试一下,如果您经常这样做,永久表的性能可能会更好。

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


Mar*_*gor 6

该解决方案基于 MySQL 相同问题的精彩答案。它在 MSSQL 上的性能也非常好。/sf/answers/151044351/

select DateGenerator.DateValue from (
  select DATEADD(day, - (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)), CONVERT(DATE, GETDATE()) ) as DateValue
  from (select a.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as a(a)) as a
  cross join (select b.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as b(a)) as b
  cross join (select c.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as c(a)) as c
  cross join (select d.a 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
Run Code Online (Sandbox Code Playgroud)

仅适用于过去的日期,适用于未来的日期更改 DATEADD 函数中的减号。查询仅适用于 SQL Server 2008+,但也可以通过用联合替换“从值中选择”结构来重写 2005。


KM.*_*KM. 5

要使此方法起作用,您需要执行以下一次性表设置:

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

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

SELECT
    @Start+Number-1
    FROM Numbers
    WHERE Number<=DATEDIFF(day,@Start,@End)+1
Run Code Online (Sandbox Code Playgroud)

抓住他们做:

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

输出:

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