在SQL DB2中,将跳过假日、异常和周末的N个工作日添加到给定日期



我在这里面临着一项具有挑战性的任务,花了一天时间,我只能通过一个过程来解决它,但所有项目都需要很长时间才能运行。

如果可能的话,我想在一个查询中解决它(没有函数或过程)。

在编程语言或sql函数/过程中已经存在一些问题(Wich I也解决了min)。所以我想问是否有可能只用SQL 来解决这个问题

背景信息是:

  • project
  • phase
  • holiday表格
  • 取消假日或周末(将该日期设为工作日)并与项目关联的dayexception
  • 一个项目可能有0-N个阶段
  • 相位具有start datedurationdraworder(系统需要)
  • 工作日是指所有非周末和假日的日子(如果该日期在dayexception表中,则除外)

考虑以下情况:

project |           phase(s)                        |  Dayexception     |  Holiday
id    | id   pid    start     duration  draworder | pid       date    |   date
1    | 1     1   2014-01-20     10         0     |  1     2014-01-25 | 2014-01-25
| 2     1   2014-02-17     14         2     |                   |    

project id 1phase id 1ENDDATE实际上是2014-01-31,参见下面生成的数据:以下数据上的日期(现在开始)格式为dd/mm/yyyy(巴西格式),值Nnull

proj  pha     start          day      weekday  dayexcp      holiday  workday
1     1    20/01/2014    20/01/2014     2        N            N        1
1     1    20/01/2014    21/01/2014     3        N            N        1
1     1    20/01/2014    22/01/2014     4        N            N        1
1     1    20/01/2014    23/01/2014     5        N            N        1
1     1    20/01/2014    24/01/2014     6        N            N        1
1     1    20/01/2014    25/01/2014     7    25/01/2014   25/01/2014   1
1     1    20/01/2014    26/01/2014     1        N            N        0
1     1    20/01/2014    27/01/2014     2        N        27/01/2014   0
1     1    20/01/2014    28/01/2014     3        N            N        1
1     1    20/01/2014    29/01/2014     4        N            N        1

为了生成上述数据,我创建了一个视图daysOfYear,其中包含2014年和2015年的所有天数(它可以更大或更小,创建时包含两年的年度周转情况),并带有CTE查询。如果你们想看,请告诉我,我会在这里添加它。以及以下选择语句:

select ph.project_id proj, 
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where ph.project_id = 1
and ph.id = 1
and dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days)
order by ph.project_id, start, dy.curday, draworder

为了解决这个问题,我创建了以下查询:

select project_id, 
min(start), 
max(day) + sum(case when workday=0 then 1 else 0 end) days as enddate
from project_phase_days /*(view to the above select)*/

这将正确返回:

proj     start           enddate
1     20/01/2014      31/01/2014

我无法解决的问题是,我在最后一个结束日期(max(day))中添加的天数(非工作日sum(case when workday=0 then 1 else 0 end) days)是否为周末、节假日或例外。

请参阅以下场景(以下阶段的持续时间为7):

proj   pha     start          day      weekday  dayexcp   holiday  workday
81    578    14/04/2014    14/04/2014     2        N        N        1
81    578    14/04/2014    15/04/2014     3        N        N        1
81    578    14/04/2014    16/04/2014     4        N        N        1
81    578    14/04/2014    17/04/2014     5        N        N        1
81    578    14/04/2014    18/04/2014     6        N    18/04/2014   0
81    578    14/04/2014    19/04/2014     7        N                 0
81    578    14/04/2014    20/04/2014     1        N    20/04/2014   0
/*the below data I added to show the problem*/              
81    578    14/04/2014    21/04/2014     2        N    21/04/2014   0
81    578    14/04/2014    22/04/2014     3        N                 1
81    578    14/04/2014    23/04/2014     4        N                 1
81    578    14/04/2014    24/04/2014     5        N                 1 

有了以上数据,我的查询将返回

proj     start       enddate
81    14/04/2014   23/04/2014

但正确的结果是enddate24/04/2014,这是因为我的查询没有考虑最后一天之后的日子是周末还是假期(或例外情况),正如你在数据集中看到的那样,在我的持续时间之外的21/04/2014也是假期。

我还尝试在phase表上创建一个CTE,为每次迭代添加一天,直到持续时间结束,但我无法添加exceptionsholidays,因为DB2不允许我在CTE递归上添加左联接。像这样:

with CTE (projectid, start, enddate, duration, level) as (
select projectid, start, start as enddate, duration, 1
from phase 
where project_id=1
and phase_id=1
UNION ALL
select projectid, start, enddate + (level days), duration, 
case when isWorkDay(enddate + (level days)) then level+1 else level end as level
from CTE left join dayexception on ...
left join holiday on ...
where level < duration
) select * from CTE

PS:由于DB2的限制,上面的查询不起作用,isWorkDay只是一个例子(这将是关于dayexception和holiday表值的情况)。

如果你有任何疑问,请在评论中询问。如有任何帮助,我们将不胜感激。谢谢

如何向前和向后计算工作日

背景上个世纪,我在一家使用这种技术的公司工作。这是一个伪代码答案。这对他们的目的非常有效。

您需要的是一个包含日期列和递增一的id列的表。表格中只填写营业日期。。。这是棘手的部分,因为观察日期在另一个日期。就像2017-01-02是我工作的一个假期,但它并不是公认的AFAIK假期。

如何在未来获得200个工作日。

  1. 选择最小值(id),其中日期>=到当前日期
  2. 选择id=id+200的日期

如何在过去获得200个工作日。

  1. 从具有日期>=到当前日期的表中选择最小值(id)
  2. 选择id=id-200的日期

之间的工作日。

select count(*) from myBusinessDays where "date" between startdate and enddate

祝你好运,因为这是伪代码。

因此,使用@danny117答案的想法,我能够创建一个查询来解决我的问题。不完全是他的想法,但它给了我解决它的方向,所以我会把它标记为正确的答案,这个答案是分享解决它的实际代码

首先,让我分享一下我对这些时期的看法。正如我所说,我用2014年和2015年的数据创建了一个viewdaysofyear(在我的最终解决方案中,我添加了一个相当大的间隔,而不会影响最终结果)。Ps:这里的日期格式是巴西格式dd/mm/yyyy

create or replace view daysofyear as 
with CTE (curday, year, weekday) as (
select a1.firstday, year(a1.firstday), dayofweek(a1.firstday)
from (select to_date('01/01/1990', 'dd/mm/yyyy') firstday 
from sysibm.sysdummy1) as a1
union all
select a.curday + 1 day as sumday, 
year(a.curday + 1 day), 
dayofweek(a.curday + 1 day)
from CTE a
where a.curday < to_date('31/12/2050', 'dd/mm/yyyy')
)
select * from cte;

使用该视图,我创建了另一个视图,并根据我的历史数据添加了天数(较大的阶段+相当大的裕度)

create or replace view project_phase_days as
select ph.project_id proj, 
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
ph.duration,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days) + 200 days 
/*max duration in database is 110*/

之后,我创建了这个查询:

select p.id,
a.start,
a.curday as enddate
from project p left join
(
select p1.project_id,
p1.duration,
p1.start,
p1.curday,
row_number() over (partition by p1.project_id 
order by p1.project_id, p1.start, p1.curday) rorder
from project_phase_days p1
where p1.validday=1
) as a
on (p.id = a.project_id
and a.rorder = a.duration)
order by p.id, a.start

它所做的是根据project_id, start date and current day (curday)排序的project_id,从我的视图中选择所有工作日(与我的其他日期视图相结合)行号,然后与project table相结合,得到解决问题的技巧部分,即a.rorder = a.duration

如果你们需要更多的解释,我很乐意提供。

最新更新