【问题标题】:Split DATEDIFF into separate months将 DATEDIFF 拆分为单独的月份
【发布时间】:2019-12-05 02:55:09
【问题描述】:

我目前有一个问题,即我在开始日期和结束日期之间的几分钟内执行 DATEDIFF,但是当这个日期进入新的月份时,我需要将每个月的数字分开。

请查看示例数据(文本和图像视图);

SELECT [BookingNum]
  ,[StartDate]
  ,[EndDate]
  ,[Location]
  ,DATEPART(m,startdate) AS [Month]
  ,DATEDIFF(MINUTE,StartDate,EndDate) AS [Minutes]
FROM [Test].[dbo].[Booking]


BookingNum  StartDate                  EndDate        Location Month Minutes
   1    2019-02-05 12:54:00.000 2019-02-08 15:00:00.000 Area 1  2   4446
   2    2019-05-02 10:41:00.000 2019-05-10 12:39:00.000 Area 2  5   11638
   3    2019-06-01 10:30:00.000 2019-06-04 09:25:00.000 Area 3  6   4255
   4    2019-02-02 09:41:00.000 2019-04-20 11:54:00.000 Area 1  2   111013
   5    2019-03-29 19:09:00.000 2019-04-02 10:41:00.000 Area 3  3   5252

对于第 4 行和第 5 行,当它们跨越多个月时,需要额外的行。

我想看看第 4 行中的数据示例;

StartDate                  EndDate            Location Month Minutes
2019-02-02 09:41:00.000 2019-02-28 23:59:00.000 Area 1  2   38298
2019-03-01 00:00:00.000 2019-03-31 23:59:00.000 Area 1  3   44639
2019-04-01 00:00:00.000 2019-04-20 23:59:00.000 Area 1  4   28074

这将给我该月的总分钟数,仅在开始日期和结束日期之间。

非常感谢任何帮助。

【问题讨论】:

  • 用您正在使用的数据库标记问题。您当前的代码也很有帮助。
  • 请标记您的 DBMS(MySQL、SQL Server 等)。另外,最好不要链接到图片,纯文本是好的。
  • 谢谢,我添加了 SQL Server。

标签: sql sql-server datediff


【解决方案1】:

要实现这一点,您需要创建一个额外的表来加入包含月份的表。然后,您将加入该表,其中日期的月份位于日历表中的日期之间,为此,您需要使用 dateadd/datediff 函数将日期四舍五入到当月的第一天,例如:DATEADD(month, DATEDIFF(month, 0, StartDate),0)。这是通过计算某个随机开始日期(在本例中为 0,即 1/1/1900)之间的月差,然后将这些月份加回到开始日期来实现的。

然后,如果您的开始或结束日期与日历表记录不在同一月份,则您需要将开始或结束日期向上或向下舍入到月底,这将允许您重新计算时间。

整个代码如下所示:

CREATE TABLE #MonthDate
(MonthDate date PRIMARY KEY);

INSERT INTO #MonthDate (MonthDate)
VALUES ('20190101'),('20190201'),('20190301'),('20190401'),('20190501'),('20190601');

WITH RoundedDates As 
(SELECT b.StartDate,
B.EndDate,
DATEADD(month, DATEDIFF(month, 0, b.StartDate),0)  AS RoundedStartDate,
DATEADD(month, DATEDIFF(month, 0, b.EndDate),0)  AS RoundedEndDate
FROM Test.dbo.Booking AS b)
SELECT rd.StartDate
, rd.EndDate
, DATEDIFF(minute, CASE WHEN rd.RoundedStartDate = md.MonthDate THEN rd.StartDate ELSE md.MonthDate END, CASE WHEN rd.RoundedEndDate = md.MonthDate THEN rd.EndDate ELSE DATEADD(month,1,md.MonthDate) END) AS Minutes
FROM RoundedDates AS rd
INNER JOIN #MonthDate as md
   ON md.MonthDate BETWEEN rd.RoundedStartDate AND rd.RoundedEndDate

http://sqlfiddle.com/#!18/70730/2

【讨论】:

    【解决方案2】:

    编辑:递归 CTE 应该可以解决问题!基本上,使用递归来通过 EOM 和结束日期中的较小者来获取开始日期,直到最终到达结束日期。

    Fiddle

    DECLARE @tbl TABLE (bookingnum INT, sd DATETIME, ed DATETIME)
    INSERT INTO @tbl VALUES 
    (1, '2/5/2019 12:54 PM', '2/8/2019 3:00 PM'),
    (2, '5/2/2019 10:41 AM', '5/10/2019 12:39 PM'),
    (3, '6/1/2019 10:30 AM', '6/4/2019 9:25 AM'),
    (4, '2/2/2019 9:41 AM', '5/20/2019 11:54 AM'),
    (5, '3/29/2019 7:09 PM', '4/2/2019 10:41 AM')
    
    ;WITH cte AS (
        SELECT bookingnum, sd, DATEADD(DAY, 1, EOMONTH(sd)) eom, ed,
            CASE WHEN DATEADD(DAY, 1, EOMONTH(sd)) < ed THEN DATEADD(DAY, 1, EOMONTH(sd)) else ed END AS applied_ed
        FROM @tbl
        UNION ALL 
        SELECT bookingnum, applied_ed, DATEADD(DAY, 1, EOMONTH(applied_ed)) eom, ed,
            CASE WHEN DATEADD(DAY, 1, EOMONTH(applied_ed)) < ed THEN DATEADD(DAY, 1, EOMONTH(applied_ed)) else ed END AS applied_ed
        FROM cte
        WHERE applied_ed < ed
    )
    SELECT bookingnum, sd, applied_ed AS ed, DATEDIFF(MINUTE, sd, applied_ed) minutes
    FROM cte
    ORDER BY bookingnum, sd
    

    返回:

    bookingnum  sd                      ed                          minutes
    1           2019-02-05 12:54:00.000 2019-02-08 15:00:00.000     4446
    2           2019-05-02 10:41:00.000 2019-05-10 12:39:00.000     11638
    3           2019-06-01 10:30:00.000 2019-06-04 09:25:00.000     4255
    4           2019-02-02 09:41:00.000 2019-03-01 00:00:00.000     38299
    4           2019-03-01 00:00:00.000 2019-04-01 00:00:00.000     44640
    4           2019-04-01 00:00:00.000 2019-05-01 00:00:00.000     43200
    4           2019-05-01 00:00:00.000 2019-05-20 11:54:00.000     28074
    5           2019-03-29 19:09:00.000 2019-04-01 00:00:00.000     3171
    5           2019-04-01 00:00:00.000 2019-04-02 10:41:00.000     2081
    

    【讨论】:

    • 只是在非示例数据上使用了这个,它可以跨越很多个月,但是如果跨越 3 个不同的月份,这将只计算开始日期月份和结束日期月份。我怎么能改变它来捕捉中间的任何月份?
    • 如果您查看下面的解决方案,这将扩展完整的结果集。
    【解决方案3】:

    这可以使用递归 CTE 来实现,如下所示。这会计算 startdate 和 enddate 之间的多个月。 小提琴:http://sqlfiddle.com/#!18/26568/4

    create table #temp(
    BookingNum int,
    StartDate datetime,
    EndDate datetime,
    Location varchar(25),
    )
    
    insert into #temp
    values(1,'2019-02-05 12:54:00','2019-02-08 15:00:00','Area 1'),
    (2,'2019-05-02 10:41:00','2019-05-10 12:39:00','Area 2'),
    (3,'2019-06-01 10:30:00','2019-06-04 09:25:00','Area 3'),
    (4,'2019-02-02 09:41:00','2019-05-20 11:54:00','Area 1'),
    (5,'2019-03-29 19:09:00','2019-04-02 10:41:00','Area 3')
    
    
    ;WITH cte AS
      (
        SELECT BookingNum,
             StartDate,
             CASE
                WHEN DATEPART(m, EndDate) > DATEPART(m, startdate)
                THEN DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, startdate) + 1, 0))
                ELSE EndDate
             END AS EndDate,
             Location,
             DATEPART(m, EndDate) - DATEPART(m, startdate) AS MonthDiff
        FROM #temp
    
        UNION ALL
    
        SELECT cte.BookingNum,
             CASE
                WHEN cte.MonthDiff > 0
                THEN DATEADD(month, DATEDIFF(month, 0, DATEADD(month, 1, cte.StartDate)), 0)
                ELSE cte.StartDate
             END AS startDate,
             CASE
                WHEN cte.MonthDiff > 0  AND DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, 1, cte.StartDate)) + 1, 0)) < t.EndDate
                THEN DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, 1, cte.StartDate)) + 1, 0))
                ELSE t.EndDate
             END AS EndDate,
             cte.Location,
             (cte.MonthDiff - 1) MonthDiff
        FROM cte
            INNER JOIN #temp t ON cte.BookingNum = t.BookingNum
        WHERE cte.MonthDiff > 0
    )
    SELECT BookingNum,
          StartDate,
           EndDate,
           Location,
           DATEPART(m, startdate) AS month,
           DATEDIFF(minute, startdate, enddate) AS minutes  
    FROM cte
    ORDER BY 1;
    
    
    
    drop table #temp
    

    结果:

    BookingNum  StartDate               EndDate                 Location                  month       minutes
    ----------- ----------------------- ----------------------- ------------------------- ----------- -----------
    1           2019-02-05 12:54:00.000 2019-02-08 15:00:00.000 Area 1                    2           4446
    2           2019-05-02 10:41:00.000 2019-05-10 12:39:00.000 Area 2                    5           11638
    3           2019-06-01 10:30:00.000 2019-06-04 09:25:00.000 Area 3                    6           4255
    4           2019-02-02 09:41:00.000 2019-02-28 23:59:59.000 Area 1                    2           38298
    4           2019-03-01 00:00:00.000 2019-03-31 00:00:00.000 Area 1                    3           43200
    4           2019-04-01 00:00:00.000 2019-04-30 00:00:00.000 Area 1                    4           41760
    4           2019-05-01 00:00:00.000 2019-05-20 11:54:00.000 Area 1                    5           28074
    5           2019-03-29 19:09:00.000 2019-03-31 23:59:59.000 Area 3                    3           3170
    5           2019-04-01 00:00:00.000 2019-04-02 10:41:00.000 Area 3                    4           2081
    

    【讨论】:

    • 如何更改它以计算结束日期月份,目前它只计算整个结束日期月份而不是实际结束日期的时间?
    • 我倾向于避免递归 ctes,因为它们在性能方面不能很好地扩展。
    • 我已经更新了代码。现在应该采用正确的结束日期
    猜你喜欢
    • 2020-10-19
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多