计算两个日期之间的差异

时间:2009-04-23 19:33:19

标签: sql sql-server database

我有一份报告可以计算多个日期差异(在工作日内,而不是DATEDIFF),原因是各种商业原因过于沉闷而无法进入。

基本上,查询(现在)看起来像

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;

我可以将此计算更改为使用标量函数,但我不希望标量函数对结果集中的每一行执行4次。

我在数据库中有一个Calendar表:

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

表值函数和CROSS APPLY会是一个不错的选择吗?如果是这样,我将如何写这样的东西?或者标量函数是我最好的选择?

重要提示我们数据库中的所有日期值都已被删除,因此可以安全地忽略任何会将日期重置为午夜的代码。

4 个答案:

答案 0 :(得分:3)

实际上我认为你想要使用标量函数。乍一看,您需要做一些计算。然后我想到了更多,你实际上可以通过两个步骤完成这一过程。

1。)将您的日期值回滚到相应日期的午夜,这样您就可以轻松搞清楚。由于提供了额外信息,因此不需要这样做!

2。)执行查询以查明当天值之间不存在假期的工作日数

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

总的来说,我认为最有效的方法是将其作为标量函数执行,我确信可能还有其他方法,但这种方式很简单,只要你在Calendar表上有一个索引就可以了性能不应太差。

关于交叉申请的说明

做一些看,这也可以通过交叉应用来完成,但实际上它最终会做同样的事情,所以我认为Scalar函数是一个更好的解决方案,因为它更容易理解,并且很容易重复的。

答案 1 :(得分:2)

技巧是使用内联表值函数,因为它们不会像标量函数那样遭受相同的性能损失。它们相当于将函数的源代码实际粘贴到查询中。

以下是它的工作原理:

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;

这应该表现得非常好,因为它与将子查询从函数中取出并将其粘贴到主查询的select子句中相同。它比标量函数更快。

答案 2 :(得分:0)

我也建议您使用标量函数。以下是我从here偷走的功能。这样你只需要保留一个假期表并减去开始和结束日期之间的数字。

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

答案 3 :(得分:0)

以下版本基于以上版本,适用于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 ;