2009-04-23 152 views
2

我有一個報告可以計算多種日期差異(在工作日內,而不是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次結果集。

我在數據庫中有一個萬年曆錶:

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

會表值函數和CROSS APPLY在這裏一個好的選擇嗎?如果是這樣,我會如何去寫這樣的事情?或者是一個標量函數我最好的選擇?

重要注意事項我們的數據庫中的所有日期值都已被刪除,因此忽略任何將日期重置爲午夜的代碼是安全的。

回答

3

實際上,我認爲你想用這個標量函數去做。乍一看,你將需要做一些計算。然後,我更多地考慮了這個問題,實際上你可以通過兩步過程簡單地做到這一點。

1.) 將您的日期值恢復到相應日子的午夜時間,這樣您可以輕鬆搞清楚。 由於提供了額外的信息,這不是必需的!

2)執行查詢以找出有多少工作日,不屬於天之間存在節假日值

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

總的來說,我認爲最有效的方式是隻做到這一點作爲一個標量函數,我相信可能有其他的方式,但這種方式很簡單,只要你在日曆表上有一個索引,它應該不會太糟糕的性能明智。

注跨應用

做一點看,這也可以通過交叉進行申請,但真正最終它做同樣的事情,所以我認爲標量函數是一個更好的因爲它更容易理解,並且易於重複。

+0

你知道的工作,我應該提到,我所有的日期是在午夜。我會在頂部添加。 – 2009-04-23 19:49:27

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 
+0

這有效,但我擔心的是,您正在執行多個計算和日期時間操作。事實上,他已經有了一個日曆表,它可能會更容易直接利用它。 (但是,我會記下這個信息,以便稍後在我的項目中使用......) – 2009-04-23 19:53:41

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子句。它會比標量函數更快。

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 ; 
相關問題