【问题标题】:Return time between two dates except weekends两个日期之间的返回时间,周末除外
【发布时间】:2020-09-15 18:22:59
【问题描述】:

我需要在Oracle 中返回两个日期之间的时间,除了周末的时间,我可以返回分钟。但是,当我设置周末日期时,我会收到 null 结果,而不是工作周的剩余时间。

首先,我们需要创建一个函数:

CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
RETURN NUMBER
IS
    v_return NUMBER;
BEGIN
    select  sum(greatest(end_dt - start_dt,0)) * 24 * 60 work_minutes
      into  v_return
      from  dual
      where trunc(start_dt) - trunc(start_dt,'iw') < 5; -- exclude weekends
    RETURN v_return;
END;

案例 1 - 返回工作周中的分钟数 - 好的

工作周的开始和结束。

SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('14-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'), 
                          TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
    "SYS"."DUAL";

案例 2 - 返回工作周的剩余分钟数 - 失败 从周末开始,到工作周结束。

SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('13-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'), 
                          TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
    "SYS"."DUAL";

13-09-2020 是星期天,因此我预计返回时间为星期一的 600 分钟。

在这些可能性中,我们可以从工作日开始,到周末结束。

【问题讨论】:

    标签: sql oracle datetime recursive-query


    【解决方案1】:

    您不需要使用 SQL 或行生成器,只需使用 PL/SQL 进行简单计算即可。改编自我的回答 herehere

    CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
    RETURN NUMBER
    IS
      p_start_date   DATE;
      p_end_date     DATE;
      p_working_days NUMBER;
    BEGIN
      IF start_dt IS NULL OR end_dt IS NULL THEN
        RETURN NUll;
      END IF;
    
      -- Enforce that the values are earliest start date to latest end date.
      p_start_date := LEAST( start_dt, end_dt );
      p_end_date   := GREATEST( start_dt, end_dt );
    
      -- Calculate the number of days from the beginning of the ISO week containing
      -- the start date and the beginning of the ISO week containing the end date
      -- and then multiply this by 5/7 to get the number of full business days.
      --
      -- Then add on the extra days from the beginining of the ISO week containing
      -- the end date and the end date and subtract the extra days from the
      -- beginning of the ISO week containing the start date to the start date.
      p_working_days := ( TRUNC( p_end_date, 'IW' ) - TRUNC( p_start_date, 'IW' ) ) * 5 / 7
                        + LEAST( p_end_date - TRUNC( p_end_date, 'IW' ), 5 )
                        - LEAST( p_start_date - TRUNC( p_start_date, 'IW' ), 5 );
    
      -- If the start date and end date are reversed then return a negative value.
      IF start_dt > end_dt THEN
        RETURN -ROUND( p_working_days * 24 * 60, 3 );
      ELSE
        RETURN +ROUND( p_working_days * 24 * 60, 3 );
      END IF;
    END;
    /
    

    然后:

    SELECT GET_BUS_MINUTES_BETWEEN(
             DATE '2020-09-14' + INTERVAL '6' HOUR, 
             DATE '2020-09-14' + INTERVAL '10' HOUR
           ) AS minutes_between
    FROM   DUAL;
    

    输出:

    | MINUTES_BETWEEN | | --------------: | | 240 |

    和:

    SELECT GET_BUS_MINUTES_BETWEEN(
             DATE '2020-09-13' + INTERVAL '6' HOUR, 
             DATE '2020-09-14' + INTERVAL '10' HOUR
           ) AS minutes_between
    FROM   DUAL;
    

    输出:

    | MINUTES_BETWEEN | | --------------: | | 600 |

    db小提琴here

    【讨论】:

      【解决方案2】:

      如果间隔不是太大,一种方法使用蛮力方法生成范围内的所有分钟,然后排除周末:

      with cte(dt, end_dt) as (
          select start_dt, end_dt from dual
          union all
          select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
      )
      select count(*) work_minutes
      from cte
      where trunc(dt) - trunc(dt,'iw') < 5
      

      如果间隔不是太大,一种方法使用蛮力方法生成范围内的所有分钟,然后排除周末:

      with cte(dt, end_dt) as (
          select start_dt, end_dt from dual
          union all
          select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
      )
      select count(*) work_minutes
      from cte
      where to_char(dt, 'IW') <= 5
      

      如果你有较大的间隔,我们可以通过预先生成分钟/小时序列来减少迭代次数:

      with 
          params (start_dt, end_dt) as (
              select start_dt, end_dt from dual
          )
          minutes (mi) as (
              select 0 from dual
              union all select mi + 1 from minutes where mi < 59
          ),
          hours (hr) as (
              select 0 from dual
              union all select hr + 1 from hours where hr < 23
          )
      select count(*) work_minutes
      from params p
      cross join minutes m
      cross join hours h
      where 
          p.start_dt + h.hr / 24 + m.mi / 24 / 60 <= end_dt
          and trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60) - trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60,'iw') < 5
      

      【讨论】:

      • 嘿@GMB,不错的解决方案!我正在从13-09-2020...14-09-2020 进行测试,函数返回0。SELECT "RAFAEL_LIMA"."GET_BUS_MINUTES_BETWEEN"(TO_DATE('13-09-2020 00:00:00', 'dd-mm-yyyy hh24:mi:ss'), TO_DATE('14-09-2020 12:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES" FROM "SYS"."DUAL";
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-17
      • 2019-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-14
      • 1970-01-01
      相关资源
      最近更新 更多