【问题标题】:Add N business days to a given date skipping holidays, exceptions and weekends in SQL DB2将 N 个工作日添加到给定日期,在 SQL DB2 中跳过节假日、例外情况和周末
【发布时间】:2017-01-10 20:12:21
【问题描述】:

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

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

在编程语言或 sql 函数/过程中已经存在一些问题(我也解决了 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 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语句:

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 as 24/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 表值的情况)。

如果您有任何疑问,请在 cmets 中询问。 任何帮助将不胜感激。谢谢。

【问题讨论】:

    标签: sql db2


    【解决方案1】:

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

    上个世纪的背景 我曾在这家使用这种技术的公司工作。所以这是一个伪代码答案。它非常适合他们的目的。

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

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

    1. 选择日期 >= 到当前日期的 min(id)。
    2. 选择 id=id+200 的日期。

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

    1. 从日期 >= 到当前日期的表中选择 min(id)。
    2. 选择 id=id-200 的日期。

    工作日之间。

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

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

    【讨论】:

    • 这实际上是一个不错的主意。虽然不需要ID 列,但可以使用ROW_NUMBERROWID 或类似的东西,具体取决于RDBM。我喜欢的想法是在工作日有一个表格/视图。我可以生成排除/添加非工作日或例外情况。谢谢你。我会在星期一回去工作时尝试一下,我会分享这个经验。 +1
    • 行号,如果你可以直接通过范围蠕变细节。 ?
    • 你好@danny117。我已经添加了我的最终解决方案,我会将您的答案标记为正确的答案,因为它给出了解决我的问题的中心思想。谢谢。
    【解决方案2】:

    因此,使用@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 排序的project_id, start date and current day (curday) 从我的视图中选择所有工作日(与我的其他日子视图一起)我然后加入project table 以获得解决问题的技巧部分这是a.rorder = a.duration

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多