【问题标题】:Numbers of weekdays in a date range in TSQLSQL中日期范围内的工作日数
【发布时间】:2021-06-15 03:25:06
【问题描述】:

这比看起来更难。我需要一个函数来计算日期范围内给定工作日的数量。我不想要任何循环或递归 SQL。有数以百万计的例子就是这样做的。我需要一个快速的计算函数。

函数的输入是 weekday, 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

【问题讨论】:

  • 很抱歉投票结束。误读了问题。
  • 我自己的版本现在可以使用,将其作为答案发布是错误的还是应该将其添加为问题的一部分?它与当前的答案非常不同。
  • 您可以将其作为答案发布,我认为您也应该能够接受自己的答案。我认为您可以接受它是有时间限制的。
  • 米凯尔。谢谢。我认为不应允许人们选择自己的答案。那会导致滥用。同样在这种情况下,我考虑的时间比你要长得多。我没有资格估计哪个功能最快。在这种情况下,这将是我将使用的功能。如何比较它们有什么好的建议吗?
  • 尝试使用数字表进行性能测试并执行该函数 100000 次。花了 10 秒,我们两个版本之间的差异是 60 毫秒。不确定测试是否好:)。

标签: sql sql-server-2008 tsql math


【解决方案1】:
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 这很好用。很花哨。当我试图解决它时,我自己有一个更原始的方法。
  • 我非常喜欢这种方法,但我认为你做得有点过头了。请看看我的解决方案。
  • @Andriy - 我同意。它几乎表明了我的想法。我没有花时间清理它。但是,我们的版本不会做同样的事情。你的版本需要set datefirst 7,我的版本需要set datefirst 1。测试dbo.f_countweekdays(5, '2011-07-11', '2011-07-17')
  • 您是对的,非常感谢您指出这一点!我不记得遇到过我在更新的答案中谈到的 DATEDIFF 的特殊性。提前知道这一点非常好。
  • 非常抱歉,我接受 Andriy M 的解决方案是最好的。我希望你没问题。
【解决方案2】:

@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

【讨论】:

  • 对不起,我离开了互联网,我也加了 1。很不错
【解决方案3】:

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

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

如果您对一个已知的日期范围进行大量日期分析,这确实可以加快和简化您的查询。您是否提前知道可能的日期范围是限制这是否有用的因素,真的,但这是一个有用的技巧。

【讨论】:

  • 感谢您的回答。对于 1 个小功能来说,这似乎需要做很多工作。
  • 一个小功能需要做很多工作。不过,您越想对日期做任意事情,它就越有用,所以我想我会提到它。
  • 工作量很小,而且很简单。在阅读了此处的其他解决方案以及将我推荐到此处的解决方案之后,由于争论的程度、关于 DATEFIRST 设置的讨论以及理解它们所需的专业知识水平,我无法决定使用其中的哪一个。所以我选择了我理解的东西。
猜你喜欢
  • 2014-01-07
  • 2013-05-02
  • 2021-08-16
  • 1970-01-01
  • 2015-05-24
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 2019-04-09
相关资源
最近更新 更多