【问题标题】:Get all dates between two dates in SQL Server获取 SQL Server 中两个日期之间的所有日期
【发布时间】:2014-06-11 00:15:23
【问题描述】:

如何获取两个日期之间的所有日期?

我有一个变量@MAXDATE,它存储表中的最大日期。现在我想获取@MaxdateGETDATE() 之间的所有日期,并希望将这些日期存储在游标中。

到目前为止,我做了以下工作:

;with GetDates As  
(  
    select DATEADD(day,1,@maxDate) as TheDate
    UNION ALL  
    select DATEADD(day,1, TheDate) from GetDates  
    where TheDate < GETDATE()  
)  

这很好,但是当我试图将这些值存储在光标中时

SET @DateCurSor = CURSOR FOR
                SELECT TheDate
                FROM GetDates

编译错误

关键字“SET”附近的语法不正确。

如何解决?

【问题讨论】:

  • 到底为什么你想要一个光标?!?您应该尽量避免使用光标
  • 情况是这样,我必须使用CURSOR。
  • W H Y ? ? ? 我 99% 确定您不必必须使用光标!如果你不使用光标会更好!
  • 我有一个包含日期、项目代码和数量的表格。假设该表的数据如下: date ||it_cd||qty 24-04-14||i-1 ||10 26-04-14||i-1 ||20 现在我将如何获得 qty于 2014 年 4 月 28 日
  • 如果您想显示表格,请编辑您的问题并添加。 cmets 中没有格式。

标签: sql sql-server


【解决方案1】:

我的第一个建议是使用您的calendar table,如果您没有,请创建一个。它们非常有用。那么您的查询就很简单了:

DECLARE @MinDate DATE = '20140101',
        @MaxDate DATE = '20140106';

SELECT  Date
FROM    dbo.Calendar
WHERE   Date >= @MinDate
AND     Date < @MaxDate;

如果您不想或无法创建日历表,您仍然可以在没有递归 CTE 的情况下即时执行此操作:

DECLARE @MinDate DATE = '20140101',
        @MaxDate DATE = '20140106';

SELECT  TOP (DATEDIFF(DAY, @MinDate, @MaxDate) + 1)
        Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @MinDate)
FROM    sys.all_objects a
        CROSS JOIN sys.all_objects b;

如需进一步阅读,请参阅:

关于然后在光标中使用这个日期序列,我真的建议你找到另一种方法。通常有一个基于集合的替代方案会表现得更好。

所以你的数据:

  date   | it_cd | qty 
24-04-14 |  i-1  | 10 
26-04-14 |  i-1  | 20

要获得 28-04-2014 的数量(我收集的是您的要求),您实际上不需要任何上述内容,您可以简单地使用:

SELECT  TOP 1 date, it_cd, qty 
FROM    T
WHERE   it_cd = 'i-1'
AND     Date <= '20140428'
ORDER BY Date DESC;

如果您不希望它用于特定项目:

SELECT  date, it_cd, qty 
FROM    (   SELECT  date, 
                    it_cd, 
                    qty, 
                    RowNumber = ROW_NUMBER() OVER(PARTITION BY ic_id 
                                                    ORDER BY date DESC)
            FROM    T
            WHERE   Date  <= '20140428'
        ) T
WHERE   RowNumber = 1;

【讨论】:

  • 我不确定交叉连接是否必要。执行查询时似乎无关紧要。
  • @PimBrouwers 交叉连接的必要性在于需要多少行。如果需要的日期少于sys.all_objects 中的对象数量,则不需要,但如果日期范围跨越 20 年,则需要交叉连接。使用TOP (Days needed) 意味着当需要较少的行时,交叉连接的开销很少或没有,因此将其留在里面没有任何害处。
  • 太棒了!谢谢你解释。非常感激。总体来说很棒的查询。
  • 这里还有一篇好文章:mssqltips.com/sqlservertip/4054/…
【解决方案2】:

您可以使用此脚本查找两个日期之间的日期。 Reference taken from this Article:

DECLARE @StartDateTime DATETIME
DECLARE @EndDateTime DATETIME

SET @StartDateTime = '2015-01-01'
SET @EndDateTime = '2015-01-12';

WITH DateRange(DateData) AS 
(
    SELECT @StartDateTime as Date
    UNION ALL
    SELECT DATEADD(d,1,DateData)
    FROM DateRange 
    WHERE DateData < @EndDateTime
)
SELECT DateData
FROM DateRange
OPTION (MAXRECURSION 0)
GO

【讨论】:

    【解决方案3】:

    只是说......这是一个更简单的方法:

    declare @sdate date = '2017-06-25'
        , @edate date = '2017-07-24';
    
    with dates_CTE (date) as (
        select @sdate 
        Union ALL
        select DATEADD(day, 1, date)
        from dates_CTE
        where date < @edate
    )
    select *
    from dates_CTE;
    

    【讨论】:

    • 对于那些使用它的人......时间和经验在我身上留下了他们的印记......CTE 非常适合伪循环,只要结果集很小(在这个例子中, 少于 30 天;我建议保持少于 90 天)....任何超过 90 天的时间,您可能应该构建一个带有一些可预测日期模式的查找表....重新了解 Leap 的逻辑年份识别 ;)
    【解决方案4】:

    轻松创建一个表值函数,该函数将返回一个包含所有日期的表。 输入日期为字符串 您可以自定义日期格式为您喜欢的“01/01/2017”或“01-01-2017”字符串格式(103,126 ...)

    试试这个

    CREATE FUNCTION [dbo].[DateRange_To_Table] ( @minDate_Str NVARCHAR(30), @maxDate_Str NVARCHAR(30))
    
    RETURNS  @Result TABLE(DateString NVARCHAR(30) NOT NULL, DateNameString NVARCHAR(30) NOT NULL)
    
    AS
    
    begin
    
        DECLARE @minDate DATETIME, @maxDate DATETIME
        SET @minDate = CONVERT(Datetime, @minDate_Str,103)
        SET @maxDate = CONVERT(Datetime, @maxDate_Str,103)
    
    
        INSERT INTO @Result(DateString, DateNameString )
        SELECT CONVERT(NVARCHAR(10),@minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,@minDate))
    
    
    
        WHILE @maxDate > @minDate
        BEGIN
            SET @minDate = (SELECT DATEADD(dd,1,@minDate))
            INSERT INTO @Result(DateString, DateNameString )
            SELECT CONVERT(NVARCHAR(10),@minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,@minDate))
        END
    
    
    
    
        return
    
    end   
    

    要执行该函数,请执行以下操作:

    SELECT * FROM dbo.DateRange_To_Table ('01/01/2017','31/01/2017')
    

    输出将是

    01/01/2017  Sunday
    02/01/2017  Monday
    03/01/2017  Tuesday
    04/01/2017  Wednesday
    05/01/2017  Thursday
    06/01/2017  Friday
    07/01/2017  Saturday
    08/01/2017  Sunday
    09/01/2017  Monday
    10/01/2017  Tuesday
    11/01/2017  Wednesday
    12/01/2017  Thursday
    13/01/2017  Friday
    14/01/2017  Saturday
    15/01/2017  Sunday
    16/01/2017  Monday
    17/01/2017  Tuesday
    18/01/2017  Wednesday
    19/01/2017  Thursday
    20/01/2017  Friday
    21/01/2017  Saturday
    22/01/2017  Sunday
    23/01/2017  Monday
    24/01/2017  Tuesday
    25/01/2017  Wednesday
    26/01/2017  Thursday
    27/01/2017  Friday
    28/01/2017  Saturday
    29/01/2017  Sunday
    30/01/2017  Monday
    31/01/2017  Tuesday
    

    【讨论】:

      【解决方案5】:

      在我的情况下,这可以被认为有点棘手,我不能使用 CTE 表,所以决定加入 sys.all_objects 然后创建行号并将其添加到开始日期直到它到达结束日期.

      请参阅下面我在 2018 年 7 月生成所有日期的代码。用您自己的变量替换硬编码的日期(在 SQL Server 2016 中测试):

      select top (datediff(dd, '2018-06-30', '2018-07-31')) ROW_NUMBER() 
      over(order by a.name) as SiNo, 
      Dateadd(dd, ROW_NUMBER() over(order by a.name) , '2018-06-30') as Dt from sys.all_objects a
      

      【讨论】:

        【解决方案6】:

        你可以试试这个:

            SET LANGUAGE SPANISH
        
        DECLARE @startDate DATE = GETDATE() -- Your start date
        DECLARE @endDate DATE = DATEADD(MONTH, 16, GETDATE()) -- Your end date
        DECLARE @years INT = YEAR(@endDate) - YEAR(@startDate)
        
        CREATE TABLE #TMP_YEARS (
            [year] INT
        )
        
        -- Get all posible years between the start and end date
        WHILE @years >= 0
        BEGIN
            INSERT INTO #TMP_YEARS
            ([year])
            SELECT YEAR(@startDate) + @years
        
            SET @years = @years - 1
        END
        
        ;WITH [days]([day]) AS -- Posible days at a month
        (
            SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL -- days lower than 10
            SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL -- days lower than 20
            SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24 UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 UNION ALL -- days lower than 30
            SELECT 30 UNION ALL SELECT 31 -- days higher 30
        ),
        [months]([month]) AS -- All months at a year
        (
            SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
        )
        SELECT CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) as [date]
          FROM #TMP_YEARS a
         CROSS JOIN [months] n -- Join all years with all months
         INNER JOIN [days] d on DAY(EOMONTH(CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + CONVERT(VARCHAR, DAY(EOMONTH(CAST(CONVERT(VARCHAR, a.[year]) + '-' + CONVERT(varchar, n.[month]) + '-15' AS DATE)))))) >= d.[day] AND -- The number of the day can't be higher than the last day of the current month and the current year
                              CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) <= ISNULL(@endDate, GETDATE()) AND -- The current date can't be higher than the end date
                              CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) >= ISNULL(@startDate, GETDATE()) -- The current date should be higher than the start date
         ORDER BY a.[year] ASC, n.[month] ASC, d.[day] ASC
        

        输出是这样的,你可以随意格式化日期:

        2019-01-24
        2019-01-25
        2019-01-26
        2019-01-27
        2019-01-28
        2019-01-29
        2019-01-30
        2019-01-31
        2019-02-01
        2019-02-02
        2019-02-03
        2019-02-04
        2019-02-05
        2019-02-06
        2019-02-07
        2019-02-08
        2019-02-09
        ...
        

        【讨论】:

          【解决方案7】:
          create procedure [dbo].[p_display_dates](@startdate datetime,@enddate datetime)
          as
          begin
              declare @mxdate datetime
              declare @indate datetime
              create table #daterange (dater datetime)
              insert into #daterange values (@startdate)
              set @mxdate = (select MAX(dater) from #daterange)
              while @mxdate < @enddate
                  begin
                      set @indate = dateadd(day,1,@mxdate)
                      insert into #daterange values (@indate)
                      set @mxdate = (select MAX(dater) from #daterange)
                  end
              select * from #daterange
          end
          

          【讨论】:

            【解决方案8】:

            我列出了 2 周后的日期。您可以使用变量 @period 或函数 datediff(dd, @date_start, @date_end)

            declare @period INT, @date_start datetime, @date_end datetime, @i int;
            
            set @period = 14
            set @date_start = convert(date,DATEADD(D, -@period, curent_timestamp))
            set @date_end = convert(date,current_timestamp)
            set @i = 1
            
            create table #datesList(dts datetime)
            insert into #datesList values (@date_start)
            while @i <= @period
                Begin
                    insert into #datesList values (dateadd(d,@i,@date_start))
                    set @i = @i + 1
                end
            select cast(dts as DATE) from #datesList
            Drop Table #datesList
            

            【讨论】:

              【解决方案9】:

              这是我会使用的方法。

              DECLARE 
                  @DateFrom DATETIME = GETDATE(),
                  @DateTo DATETIME = DATEADD(HOUR, -1, GETDATE() + 2); -- Add 2 days and minus one hour
              
              
              -- Dates spaced a day apart 
              
              WITH MyDates (MyDate)
              AS (
                  SELECT @DateFrom
                  UNION ALL
                  SELECT DATEADD(DAY, 1, MyDate)
                  FROM MyDates
                  WHERE MyDate < @DateTo
                 )
              
              SELECT 
                  MyDates.MyDate
                  , CONVERT(DATE, MyDates.MyDate) AS [MyDate in DATE format]
              FROM 
                  MyDates;
              

              这是一个类似的示例,但这次日期间隔一小时,以进一步帮助理解查询的工作原理:

              -- Alternative example with dates spaced an hour apart
              
              WITH MyDates (MyDate)
              AS (SELECT @DateFrom
                  UNION ALL
                  SELECT DATEADD(HOUR, 1, MyDate)
                  FROM MyDates
                  WHERE MyDate < @DateTo
                 )
              
              SELECT 
                  MyDates.MyDate
              FROM 
                  MyDates;
              

              如您所见,查询快速、准确且用途广泛。

              【讨论】:

                【解决方案10】:

                您可以使用SQL Server recursive CTE

                DECLARE 
                    @MinDate DATE = '2020-01-01',
                    @MaxDate DATE = '2020-02-01';
                
                WITH Dates(day) AS 
                (
                    SELECT CAST(@MinDate as Date) as day
                    UNION ALL
                    SELECT CAST(DATEADD(day, 1, day) as Date) as day
                    FROM Dates
                    WHERE CAST(DATEADD(day, 1, day) as Date) < @MaxDate
                )
                SELECT* FROM dates;
                

                【讨论】:

                  【解决方案11】:
                  DECLARE @FirstDate DATE = '2018-01-01'
                  DECLARE @LastDate Date = '2018-12-31'
                  DECLARE @tbl TABLE(ID INT IDENTITY(1,1) PRIMARY KEY,CurrDate date)
                  INSERT @tbl VALUES( @FirstDate)
                  WHILE @FirstDate < @LastDate
                  BEGIN
                  SET @FirstDate = DATEADD( day,1, @FirstDate)
                  INSERT @tbl VALUES( @FirstDate)
                  END
                  INSERT @tbl VALUES( @LastDate) 
                  
                  SELECT * FROM @tbl
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2017-06-12
                    • 2013-08-09
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-01-04
                    • 1970-01-01
                    • 2012-10-17
                    相关资源
                    最近更新 更多