我有一份报告可以计算多个日期差异(在工作日内,而不是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会是一个不错的选择吗?如果是这样,我将如何写这样的东西?或者标量函数是我最好的选择?
重要提示我们数据库中的所有日期值都已被删除,因此可以安全地忽略任何会将日期重置为午夜的代码。
答案 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 ;