Google bigquery从日期开始减去任意数量的工作日

时间:2018-04-12 16:26:35

标签: sql google-bigquery

我正在尝试构建一个可以从日期中减去任意数量的工作日的函数。到目前为止,我出来了

CREATE TEMPORARY FUNCTION working_days_diff(the_date DATE, num_of_days INT64) AS
(
  CASE WHEN EXTRACT(DAYOFWEEK FROM the_date) in (2,3,4,5,6,7)
    THEN
      CASE WHEN (EXTRACT(DAYOFWEEK FROM the_date) - num_of_days) > 1
      THEN DATE_SUB(the_date, INTERVAL num_of_days DAY)
      ELSE DATE_SUB(the_date, INTERVAL (num_of_days +2) DAY)
      END
    ELSE
      DATE_SUB(the_date, INTERVAL (num_of_days +1) DAY)
  END
);

当减去的天数相隔不到一周时,这种情况很有效,如下列情况:

 select working_days_diff(DATE("2018-04-12"), 3)
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 4)
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 5)
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 6)
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 7)
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 8)

但是当我想减去的天数变大时,就会失败。 例如:

 select working_days_diff(DATE("2018-04-12"), 9) -- this should return "2018-03-30"
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 10) -- this should return "2018-03-29"
 UNION ALL
 select working_days_diff(DATE("2018-04-12"), 20) -- this should return "2018-03-15"

这个问题与我之前的问题有关: bigquery subtract 3 business days from date

2 个答案:

答案 0 :(得分:2)

欣赏减去7天(即一周)的单位总是会减去5个工作日。这是因为从任何一天开始的7天时间总是会超过整个周末。解决问题的一种方法是减去整个星期,然后使用CASE表达式处理剩余天数,就像您现在所做的那样。

CREATE TEMPORARY FUNCTION working_days_diff(the_date DATE, num_of_days INT64) AS
(
    CASE WHEN EXTRACT(DAYOFWEEK FROM the_date) in (2,3,4,5,6,7)
         THEN
         CASE WHEN (EXTRACT(DAYOFWEEK FROM the_date) - MOD(num_of_days, 7)) > 1
              THEN DATE_SUB(the_date, INTERVAL ((7*num_of_days)/5 + MOD(num_of_days, 7)) DAY)
              ELSE DATE_SUB(the_date, INTERVAL ((7*num_of_days)/5) + MOD(num_of_days, 7) + 2) DAY) END
         ELSE
             DATE_SUB(the_date, INTERVAL ((7*num_of_days)/5 + MOD(num_of_days, 7) + 1) DAY) END
);

注意:您可能需要使用以下内容将公式转换为整数类型:

DATE_SUB(the_date, INTERVAL (CAST((7*num_of_days)/5) + MOD(num_of_days, 7) + 2) AS INTEGER) DAY)

答案 1 :(得分:2)

正如您已经熟悉正确处理某些情况但不是全部情况的版本一样 - 我想到的版本将与您已经拥有的版本尽量不同(当然,假设这些情况对于那些情况是正确的)你在问题中描述过 - 所以我只是将其扩展到其他案例中)

首先 - 在您的函数中,您将num_of_days的所有条目替换为MOD(num_of_days, 5) - 这样做可以忽略所有(如果有的话)完整工作周(5个工作日) - 例如而不是9天,你只计算1天的相应日期,10天和20天0天,等等

现在你需要在那个“中间”的一天“跳”回来,因为你在前一步骤中“忽略”了这么多周 为此,您将之前的结果“包含”到 - DATE_SUB(... , INTERVAL DIV(num_of_days, 5) WEEK)

这就是全部!

所以最终版本如下

   
  #standardSQL
  CREATE TEMPORARY FUNCTION working_days_diff(the_date DATE, num_of_days INT64) AS
  (
    DATE_SUB(CASE WHEN EXTRACT(DAYOFWEEK FROM the_date) IN (2,3,4,5,6,7)
      THEN
        CASE WHEN (EXTRACT(DAYOFWEEK FROM the_date) - MOD(num_of_days, 5)) > 1
        THEN DATE_SUB(the_date, INTERVAL MOD(num_of_days, 5) DAY)
        ELSE DATE_SUB(the_date, INTERVAL (MOD(num_of_days, 5) + 2) DAY)
        END
      ELSE
        DATE_SUB(the_date, INTERVAL (MOD(num_of_days, 5) +1) DAY)
    END, INTERVAL DIV(num_of_days, 5) WEEK) 
  );

您可以使用相关示例进行测试/播放

  #standardSQL
  CREATE TEMPORARY FUNCTION working_days_diff(the_date DATE, num_of_days INT64) AS
  (
    DATE_SUB(CASE WHEN EXTRACT(DAYOFWEEK FROM the_date) IN (2,3,4,5,6,7)
      THEN
        CASE WHEN (EXTRACT(DAYOFWEEK FROM the_date) - MOD(num_of_days, 5)) > 1
        THEN DATE_SUB(the_date, INTERVAL MOD(num_of_days, 5) DAY)
        ELSE DATE_SUB(the_date, INTERVAL (MOD(num_of_days, 5) + 2) DAY)
        END
      ELSE
        DATE_SUB(the_date, INTERVAL (MOD(num_of_days, 5) +1) DAY)
    END, INTERVAL DIV(num_of_days, 5) WEEK) 
  );
  SELECT working_days_diff(DATE("2018-04-12"), 3) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 4) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 5) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 6) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 7) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 8) UNION ALL
  SELECT working_days_diff(DATE("2018-04-12"), 9) UNION ALL-- this should return "2018-03-30"
  SELECT working_days_diff(DATE("2018-04-12"), 10) UNION ALL-- this should return "2018-03-29"
  SELECT working_days_diff(DATE("2018-04-12"), 20) -- this should return "2018-03-15"

现在按预期结果

Row f0_  
1   2018-04-09   
2   2018-04-06   
3   2018-04-05   
4   2018-04-04   
5   2018-04-03   
6   2018-04-02   
7   2018-03-30   
8   2018-03-29   
9   2018-03-15   

我觉得 - 这可以进一步优化一点 - 但我的目标是不要这样做 - 而是让它尽可能接近你已经得到的 - 所以你会更容易吸收和进一步修改并在实际使用案例中根据需要使用