简体   繁体   English

计算两个日期之间的差

[英]Calculating the difference between two dates

I have a report that calculates multiple date differences (in business days, not DATEDIFF) for a variety of business reasons that are far too dull to get into. 我有一份报告,该报告会由于多种商业原因而计算出多个日期差(以工作日为单位,而不是DATEDIFF),这太过乏味了。

Basically the query (right now) looks something like 基本上查询(现在)看起来像

SELECT -- some kind of information
       DATEDIFF(dd, DateOne, DateTwo) AS d1_d2_diff,
       DATEDIFF(dd, DateOne, DateThree) AS d1_d3_diff,
       DATEDIFF(dd, DateTwo, DateThree) AS d2_d3_diff,
       DATEDIFF(dd, DateTwo, DateFour) AS d2_d4_diff
  FROM some_table;

I could change this calculation to use a scalar function, but I don't want the scalar function to be executed 4 times for every row in the result set. 我可以更改此计算以使用标量函数,但我不希望对结果集中的每一行执行4次标量函数。

I have a Calendar table in the database: 我在数据库中有一个Calendar表:

CREATE TABLE Calendar (
   Date DATETIME NOT NULL,
   IsWeekday BIT,
   IsHoliday BIT
);

Would a table-valued function and CROSS APPLY be a good choice here? 在这里,表值函数和CROSS APPLY是一个不错的选择吗? If so, how would I go about writing such a thing? 如果是这样,我将如何去写这样的东西? Or is a scalar function my best bet? 还是标量函数是我最好的选择?

Important Note All date values in our database have been stripped of time so it is safe to ignore any code that would reset days to midnight. 重要说明我们数据库中的所有日期值都被剥夺了时间,因此可以安全地忽略任何将日期重置为午夜的代码。

Realistically I think you want to go with a scalar function for this. 实际上,我认为您想为此使用标量函数。 At first glance you are going to need to do a few calculations. 乍一看,您将需要进行一些计算。 Then I thought about it more, and you can actually do this quite simply with a two step process. 然后,我考虑了更多,实际上,您可以通过两步过程非常简单地完成此操作。

1.) Roll your date values back to midnight of the respective days, that way you can easily figure it out. 1.) 将日期值回滚到各天的午夜,这样您就可以轻松地找出来。 Due to extra information provided, this is not needed! 由于提供了额外的信息,因此不需要!

2.) Execute a query to find out how many week days, that are not holidays exist between the day values 2.)执行查询以找出星期几之间有多少天,这不是假日

SELECT ISNULL(COUNT(*), 0)
FROM Calendar
WHERE [DATE] > DateOne 
    AND [DATE] < DateTwo
    AND IsWeekDay = 1
    AND IsHoliday = 0

Overall I think that the most efficient way is to just do this as a Scalar Function, I'm sure there might be other ways, but this way is straightforward, and as long as you have an index on the Calendar table it shouldn't be too bad performance wise. 总的来说,我认为最有效的方法就是将其作为标量函数执行,我敢肯定还有其他方法,但是这种方法很简单,只要您在Calendar表上有索引就不应该太糟糕的表现明智的。

note on cross apply 交叉注适用

Doing a bit of looking, this could also be done via cross apply, but really in the end it does the same thing, so I think that the Scalar function is a better solution as it is easier to understand, and is easily repeatable. 稍微看一下,这也可以通过交叉应用来完成,但最终它确实可以完成相同的事情,因此我认为Scalar函数是更好的解决方案,因为它更易于理解并且易于重复。

The trick is using an inline table valued function, since they don't suffer the same performance penalty as a scalar function. 诀窍是使用内联表值函数,因为它们不会遭受与标量函数相同的性能损失。 They are equivalent to actually pasting the source code of the function right into the query. 它们等效于将函数的源代码实际粘贴到查询中。

Here's how it works: 运作方式如下:

create function BusinessDayDiffs_fn ( 
  @DateOne datetime
, @DateTwo datetime
)
returns table
as return (
  select count(*) as numBusinessDays
  from Calendar
  where date between @DateOne and @DateTwo
    and IsWeekday = 1
    and IsHoliday = 0;
)

GO

select
  d1_d2_diff = d1_d2.numBusinessDays,
  d1_d3_diff = d1_d3.numBusinessDays,
  d2_d3_diff = d2_d3.numBusinessDays,
  d3_d4_diff = d3_d4.numBusinessDays
from some_table s
cross apply BusinessDayDiffs_fn( DateOne, DayTwo  ) d1_d2
cross apply BusinessDayDiffs_fn( DateOne, DayThree) d1_d3
cross apply BusinessDayDiffs_fn( DayTwo,  DayThree) d2_d3
cross apply BusinessDayDiffs_fn( DayTwo,  DayFour ) d2_d4;

This should perform pretty well, as it's the same as taking the subquery out of the function, and pasting it right into the select clause of the main query. 这应该执行得很好,因为这与将子查询移出函数并将其直接粘贴到主查询的select子句中相同。 It'll be WAY faster than the scalar function. 它会比标量函数快得多。

I too would suggest that you use a scalar function for this. 我也建议您为此使用标量函数。 Below is such a function that I stole from here . 下面是我从这里偷走的功能。 With that you only need to maintain a table of holidays and subtract the number that fall between your start and end date. 这样,您只需要维护一个假期表并减去开始日期和结束日期之间的数字即可。

CREATE FUNCTION dbo.fn_WeekdayDiff(@StartDate DATETIME, @EndDate DATETIME)
RETURNS INT
AS
--Calculdate weekdays between two dates
BEGIN
    --if @StartDate is AFTER @EndDate, swap them
    IF @StartDate > @EndDate
    BEGIN
        DECLARE @TempDate DATETIME
        SET @TempDate = @StartDate
        SET @StartDate = @EndDate
        SET @EndDate = @TempDate
    END

    RETURN
        --number of weeks x 5 weekdays/week
          (DATEDIFF(ww, @StartDate, @EndDate) * 5)
        --add weekdays left in current week
        + CASE DATEPART(dw, @StartDate + @@DATEFIRST) WHEN 1 THEN 5 ELSE (7 - DATEPART(dw, @StartDate + @@DATEFIRST)) END
        --subtract weekdays after @EndDate
        - dbo.fn_MaxInt(6 - DATEPART(dw, @EndDate + @@DATEFIRST), 0)
END

Below is a version based on the above that should work for MySQL 下面是基于以上内容的版本,应适用于MySQL

#
# This function calculates the total number of weekdays (inclusive)
# between the specified dates.
#
# If start date < end date, the value returned is negative
#
# Known issues - due to the inaccuracy of the MySQL WEEK detection
# boundaries across years may be incorrect
#

DELIMITER $$

DROP FUNCTION IF EXISTS `dbname`.`WeekdayDiff` $$
CREATE FUNCTION `dbname`.`WeekdayDiff` (start_date date, end_date date) RETURNS INT DETERMINISTIC
BEGIN
  DECLARE week_diff INT;
  DECLARE week_diff_add_days INT;
  DECLARE temp_date DATE;
  DECLARE multiplier INT;
  DECLARE wd_left_in_start_inclusive INT;
  DECLARE wd_left_in_end_exclusive INT;
  DECLARE wd_diff INT;

  SET multiplier = 1;

  IF start_date > end_date THEN
    SET temp_date = end_date;
    SET end_date = start_date;
    SET start_date = temp_date;
    SET multiplier = -1;
  END IF;

  # Note we subtract 1 from the dates here as
  # we want sunday to be included in the last week
  SET week_diff = (YEAR(end_date) * 52 + WEEK(end_date-1)) - (YEAR(start_date) * 52 + WEEK(start_date-1));
  SET week_diff_add_days = week_diff * 5;

  # Calculate the week days left in the start week
  SET wd_left_in_start_inclusive = GREATEST( 5 - WEEKDAY( start_date ), 0 );
  SET wd_left_in_end_exclusive = GREATEST( 4 - WEEKDAY( end_date ), 0 );

  SET wd_diff = week_diff_add_days + wd_left_in_start_inclusive - wd_left_in_end_exclusive;

  RETURN wd_diff * multiplier;
END $$

DELIMITER ;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM