SQL中日期范围内的工作日数

时间:2011-07-15 09:13:01

标签: sql sql-server-2008 tsql math

这比它看起来更难。我需要一个函数来计算日期范围内给定工作日的数量。我不想要任何循环或重复sql,有数百万个例子正在做这个,我需要一个快速的函数进行计算。

该功能的输入将是工作日,fromdata,todate

-- counting fridays
set datefirst 1
SELECT dbo.f_countweekdays(5, '2011-07-01', '2011-07-31'),
dbo.f_countweekdays(5, '2011-07-08', '2011-07-15'),
dbo.f_countweekdays(5, '2011-07-09', '2011-07-15'),
dbo.f_countweekdays(5, '2011-07-09', '2011-07-14')

预期结果:

5, 2, 1, 0

我希望有人可以提供帮助。

4 个答案:

答案 0 :(得分:4)

create function dbo.f_countweekdays
(
  @DOW int, 
  @StartDate datetime, 
  @EndDate datetime
) 
returns int
begin
  return
  ( select datediff(wk, T2.St, T2.En) -
           case when T1.SDOW > @DOW then 1 else 0 end -
           case when T1.EDOW < @DOW then 1 else 0 end
    from (select datepart(dw, @StartDate),
                 datepart(dw, @EndDate)) as T1(SDOW, EDOW)
      cross apply (select dateadd(d, - T1.SDOW, @StartDate),
                          dateadd(d, 7 - T1.EDOW, @EndDate)) as T2(St, En))
end

答案 1 :(得分:3)

@Mikael Eriksson有一个很棒的主意,但他的实现似乎有点过于复杂。

以下是我提出的内容(我想强调它基于solution by @Mikael ,主要功劳应该归功于):

ALTER FUNCTION dbo.f_countweekdays (@Dow int, @StartDate datetime, @EndDate datetime)
RETURNS int
AS BEGIN
  RETURN (
    SELECT
      DATEDIFF(wk, @StartDate, @EndDate)
      - CASE WHEN DATEPART(dw, @StartDate) > @Dow THEN 1 ELSE 0 END
      - CASE WHEN DATEPART(dw, @EndDate)   < @Dow THEN 1 ELSE 0 END
      + 1
  )
END

<强>更新

正如Mikael在其答案的评论主题中正确指出的那样,为了使上述解决方案正常工作,DATEFIRST设置必​​须设置为7(星期日)。虽然我找不到这个记录,但是快速测试显示DATEDIFF(wk)忽略了实际的DATEFIRST设置,并确实在几周内返回差异,就好像DATEFIRST始终设置为7.同时DATEPART(dw)确实尊重DATEFIRST,所以当DATEFIRST设置为7以外的值时,这两个函数会返回相互不一致的结果。

因此,必须修改上述脚本,以便在计算DATEDIFF(wk)时考虑DATEFIRST设置的不同值。令人高兴的是,在我看来,修复似乎并没有使解决方案比以前更复杂。不过要为自己判断:

ALTER FUNCTION dbo.f_countweekdays (@Dow int, @StartDate datetime, @EndDate datetime)
RETURNS int
AS BEGIN
  RETURN (
    SELECT
      DATEDIFF(wk, DATEADD(DAY, -@@DATEFIRST, @StartDate),
                   DATEADD(DAY, -@@DATEFIRST, @EndDate))
      - CASE WHEN DATEPART(dw, @StartDate) > @Dow THEN 1 ELSE 0 END
      - CASE WHEN DATEPART(dw, @EndDate)   < @Dow THEN 1 ELSE 0 END
      + 1
  )
END

已编辑-@@DATEFIRST % 7个条目已简化为-@@DATEFIRST,有人建议here

答案 2 :(得分:2)

另一种方法是良好的老式数据仓库时间维度,其中包含一个包含所有潜在日期的表格,以及您希望过滤/计算的任何有用信息:

Key       ActualDate  DayName   IsWeekday  DayNumberInYear  FinancialQuarter
20110101  1 Jan 2011  Saturday  0          1                2011 Q1
20110102  2 Jan 2011  Sunday    0          2                2011 Q1
20110103  3 Jan 2011  Monday    1          3                2011 Q1

然后加入该表并过滤,例如

SELECT 
  COUNT(*) 
FROM 
  date_dimension
WHERE
  ActualDate BETWEEN '1 Jan 2011' AND '3 Jan 2011' AND
  IsWeekday = 1

如果您在已知日期范围内进行日期分析,这可以真正加快并简化您的查询。您是否事先了解可能的日期范围是限制因素,这是否真的有用,但这是一个有用的技巧。

答案 3 :(得分:1)

这是我在尝试了很多不同的方法后想出来的。我确实花了很长时间来解决它,当我发布问题时,我还在努力解决它。我决定将它作为答案发布,因为自学习徽章,虽然我从来没有得到超过2分的答案。

alter function dbo.f_countweekdays 
( @day int, @fromdate datetime, @todate datetime )  
returns int 
begin 
RETURN (SELECT datediff(day, @fromdate, dateadd(week,datediff(week,0,@todate - 1) + 
CASE WHEN datepart(weekday,@todate) < @day THEN 0 ELSE 1 END,0) + @day - 1) / 7)
end