【问题标题】:How to generate minute intervals between two dates in T-SQL?如何在 T-SQL 中生成两个日期之间的分钟间隔?
【发布时间】:2016-11-23 15:03:07
【问题描述】:

我有一个 startTime 和 endTimes 表。我需要以分钟为单位生成这两个日期之间的间隔表。以下是一些示例数据:

declare @intervalMinutes int = 10
declare @myDates table (
myId int primary key identity,
startTime datetime,
endTime datetime
)

insert @myDates (startTime, EndTime) values ('2016-07-10 08:00','2016-07-10 09:00')
insert @myDates (startTime, EndTime) values ('2016-07-12 10:00','2016-07-12 12:00')
insert @myDates (startTime, EndTime) values ('2016-07-14 12:30','2016-07-14 14:30')

我希望看到的是为每个myId 一组间隔@intervalMinutes 的日期。

因此,如果我们将 @intervalMinutes 设置为 10,那么我会在第一行看到 2016-07-10 08:002016-07-10 09:00 之间以 10 分钟为增量的 6 个日期列表。

【问题讨论】:

  • 使用计数表并交叉应用。

标签: sql-server tsql sql-server-2014


【解决方案1】:

你可以像这样使用递归查询:

declare @intervalMinutes int = 10
declare @myDates table (
myId int primary key identity,
startTime datetime,
endTime datetime
)

DECLARE @startTime DATETIME = '2016-07-10 08:00'
DECLARE @endTime DATETIME = '2016-07-10 09:00'

;WITH CTE AS
(
    SELECT  @startTime st
    UNION   ALL
    SELECT  dateadd(MINUTE,@intervalMinutes,st) st
    FROM    cte
    where   dateadd(MINUTE,@intervalMinutes,st) < @endTime
)
INSERT INTO @myDates(startTime,endTime)
SELECT st,dateadd(MINUTE,@intervalMinutes,st) FROM cte

SELECT  * FROm @myDates

【讨论】:

  • 不要;不要使用递归 CTE 来计算即使是很小的行数。请参阅以下文章了解原因。即使是编写良好的 While 循环,它也击败了递归 CTE,并使用 8 倍的逻辑读取来启动。 sqlservercentral.com/articles/…
【解决方案2】:

正如 Gordon 所说,数字/计数表可以解决问题。但是,我使用 UDF 创建动态日期范围。

例如

Select * from [dbo].[udf-Create-Range-Date]('2016-07-10 08:00','2016-07-10 09:00','MI',10)

返回

RetVal
2016-07-10 08:00:00.000
2016-07-10 08:10:00.000
2016-07-10 08:20:00.000
2016-07-10 08:30:00.000
2016-07-10 08:40:00.000
2016-07-10 08:50:00.000
2016-07-10 09:00:00.000

UDF

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)

Returns 
@ReturnVal Table (RetVal datetime)

As
Begin
    With DateTable As (
        Select DateFrom = @DateFrom
        Union All
        Select Case @DatePart
               When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
               When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
               When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
               When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
               When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
               When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
               When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
               When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
               End
        From DateTable DF
        Where DF.DateFrom < @DateTo
    )

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)

    Return
End

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 

【讨论】:

  • 不要;不要使用递归 CTE 来计算即使是很小的行数。请参阅以下文章了解原因。即使是编写良好的 While 循环,它也击败了递归 CTE,并使用 8 倍的逻辑读取来启动。 sqlservercentral.com/articles/…
【解决方案3】:

数字表可以解决您的问题。假设您不需要超过几千行,那么这应该可以工作:

with n as (
      select row_number() over (order by (select null)) - 1 as n
      from master.spt_values
     )
select d.*,
       dateadd(minute, n.n * @intervalMinutes, d.startTime)
from @myDates d join
     n
     on dateadd(minute, n.n * @intervalMinutes, d.startTime) <= d.endTime;

【讨论】:

    【解决方案4】:

    为了补充@GordonLinoff 的好答案,Jonathan Roberts(来自 SQLServerCentral.com - 请参阅原始文章代码中修订历史中的链接)编写了一个花哨的函数,它将几乎可以处理任何事情。花盒几乎解释了它以及一些示例用法这是他文章中的代码。

    /**********************************************************************************************************************
     FUNCTION: DateRange
     Returns a table of datetime values based on the parameters
     Parameters:  
     @StartDate    :Start date of the series 
     @EndDate      :End date of the series 
     @DatePart :The time unit for @interval
         ns    : nanoseconds 
         mcs   : microseconds 
         ms    : milliseconds 
         ss    : seconds
         mi    : minutes
         hh    : hours
         dd    : days
         ww    : weeks
         mm    : months
         qq    : quarters
         yy    : years
     @Interval :The number of dateparts between each value returned
    
     Sample Calls:
         SELECT * FROM [dbo].[DateRange]('2011-01-01 12:24:35', '2011-02-01 12:24:35', 'ss', 2);
         SELECT COUNT(*) FROM [dbo].[DateRange]('2018-01-01 00:00:00', '2018-01-25 20:31:23.646', 'ms', default);
         SELECT * FROM [dbo].[DateRange]('2011-01-01', '2012-02-03', default, default);
         SELECT * FROM [dbo].[DateRange]('2012-02-03', '2011-01-01', 'dd', 7);
         SELECT DATEDIFF(ns,'2018-01-01 00:00:00.000', value),Value,* 
           FROM [dbo].[DateRange]('2018-01-01 00:00:00.000', '2018-01-01 00:00:00.00001', 'ns', 100);
    
    -----------------------------------------------------------------------------------------------------------------------
    
     Revision History:
     Rev 00 - 29 Aug 2019 - Jonathan Roberts
                          - Initial release
                          - Ref: https://www.sqlservercentral.com/scripts/a-daterange-table-valued-function
    **********************************************************************************************************************/
    CREATE FUNCTION [dbo].[DateRange] 
    (
        @StartDate datetime2, 
        @EndDate   datetime2, 
        @DatePart  nvarchar(3)='dd', 
        @Interval  int=1
    )
    RETURNS TABLE AS RETURN 
      WITH A(A) AS (SELECT 0 FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) A(A)),
           B(RowNum) AS (SELECT TOP(ABS(CASE @DatePart
                                            WHEN 'ns'  THEN DATEDIFF(ns, @EndDate, @StartDate)/@Interval
                                            WHEN 'mcs' THEN DATEDIFF(mcs,@EndDate, @StartDate)/@Interval
                                            WHEN 'ms'  THEN DATEDIFF(ms, @EndDate, @StartDate)/@Interval
                                            WHEN 'ss'  THEN DATEDIFF(ss, @EndDate, @StartDate)/@Interval
                                            WHEN 'mi'  THEN DATEDIFF(mi, @EndDate, @StartDate)/@Interval
                                            WHEN 'hh'  THEN DATEDIFF(hh, @EndDate, @StartDate)/@Interval
                                            WHEN 'dd'  THEN DATEDIFF(dd, @EndDate, @StartDate)/@Interval
                                            WHEN 'ww'  THEN DATEDIFF(ww, @EndDate, @StartDate)/@Interval
                                            WHEN 'mm'  THEN DATEDIFF(mm, @EndDate, @StartDate)/@Interval
                                            WHEN 'qq'  THEN DATEDIFF(qq, @EndDate, @StartDate)/@Interval
                                            WHEN 'yy'  THEN DATEDIFF(yy, @EndDate, @StartDate)/@Interval
                                            ELSE DATEDIFF(dd, IIF(@StartDate < @EndDate, @StartDate, @EndDate), IIF(@StartDate < @EndDate, @EndDate, @StartDate))/@Interval
                                        END) + 1)
                                ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
                           FROM A A, A B, A C, A D, A E, A F, A G, A H)   -- A maximum of 16^8 (or 2^32) rows could be returned from this inline tally
      SELECT CASE @DatePart            
                  WHEN 'ns'  THEN DATEADD(ns, T.AddAmount, @StartDate)
                  WHEN 'mcs' THEN DATEADD(mcs,T.AddAmount, @StartDate)
                  WHEN 'ms'  THEN DATEADD(ms, T.AddAmount, @StartDate)
                  WHEN 'ss'  THEN DATEADD(ss, T.AddAmount, @StartDate)
                  WHEN 'mi'  THEN DATEADD(mi, T.AddAmount, @StartDate)
                  WHEN 'hh'  THEN DATEADD(hh, T.AddAmount, @StartDate)
                  WHEN 'dd'  THEN DATEADD(dd, T.AddAmount, @StartDate)
                  WHEN 'ww'  THEN DATEADD(ww, T.AddAmount, @StartDate)
                  WHEN 'mm'  THEN DATEADD(mm, T.AddAmount, @StartDate)
                  WHEN 'qq'  THEN DATEADD(qq, T.AddAmount, @StartDate)
                  WHEN 'yy'  THEN DATEADD(yy, T.AddAmount, @StartDate)
                  ELSE            DATEADD(dd, T.AddAmount, @StartDate)
              END [Value]
        FROM B
       CROSS APPLY(VALUES (IIF(@StartDate<@EndDate, @interval*RowNum, @interval*-RowNum))) T(AddAmount)
    GO
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-28
      • 2020-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多