我在这里面临着一项具有挑战性的任务,花了一天时间,我只能通过一个过程来解决它,但所有项目都需要很长时间才能运行。
如果可能的话,我想在一个查询中解决它(没有函数或过程)。
在编程语言或sql函数/过程中已经存在一些问题(Wich I也解决了min)。所以我想问是否有可能只用SQL 来解决这个问题
背景信息是:
project
表phase
表holiday
表格- 取消假日或周末(将该日期设为工作日)并与项目关联的
dayexception
表 - 一个项目可能有0-N个阶段
- 相位具有
start date
、duration
和draworder
(系统需要) - 工作日是指所有非周末和假日的日子(如果该日期在
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 1
和phase id 1
的ENDDATE
实际上是2014-01-31
,参见下面生成的数据:以下数据上的日期(现在开始)格式为dd/mm/yyyy
(巴西格式),值N
为null
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
但正确的结果是enddate
为24/04/2014
,这是因为我的查询没有考虑最后一天之后的日子是周末还是假期(或例外情况),正如你在数据集中看到的那样,在我的持续时间之外的21/04/2014
也是假期。
我还尝试在phase
表上创建一个CTE,为每次迭代添加一天,直到持续时间结束,但我无法添加exceptions
和holidays
,因为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个工作日。
- 选择最小值(id),其中日期>=到当前日期
- 选择id=id+200的日期
如何在过去获得200个工作日。
- 从具有日期>=到当前日期的表中选择最小值(id)
- 选择id=id-200的日期
之间的工作日。
select count(*) from myBusinessDays where "date" between startdate and enddate
祝你好运,因为这是伪代码。
因此,使用@danny117答案的想法,我能够创建一个查询来解决我的问题。不完全是他的想法,但它给了我解决它的方向,所以我会把它标记为正确的答案,这个答案是分享解决它的实际代码
首先,让我分享一下我对这些时期的看法。正如我所说,我用2014年和2015年的数据创建了一个view
daysofyear
(在我的最终解决方案中,我添加了一个相当大的间隔,而不会影响最终结果)。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
如果你们需要更多的解释,我很乐意提供。