Return 两个日期之间的时间(周末除外)

Return time between two dates except weekends

我需要 return Oracle 两个日期之间的时间,周末除外,我可以 return 分钟。但是当我设置周末日期时,我收到了 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 - Return 工作周的分钟数 - 好的

在工作周开始和结束。

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 - Return 工作周的剩余分钟数 - 失败 从周末开始到工作周结束。

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 是星期天,因此我预计 return 与星期一相关的 600 分钟。

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

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

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

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

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

如果间隔较大,我们可以将迭代次数减少 pre-generating 分钟/小时系列:

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

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

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<>fiddle here