如何在oracle中查找不包括周六、周日和节假日的48个工作时间日期

How to find 48 working hours date excluding saturday,sundays and holidays in oracle

某模块要求求第48、24个工作时间

要求:

假设如果我将 2nd May 作为参数传递给函数,则 48 hours 的输出应为 27th April24 hours 的输出应为 28th APRIL(因为5月1日是假期,4月29日和30日属于周六和周日)

问题出在两个连续的假期上。例如,要创建一个 dummy 数据,我们插入 5 月 2 日作为假期,运行 3rd May 上的代码应该为 48 hours 检索 27th April28th APRIL 对于 24 hours.

但我的功能似乎不适用于连续的假期。某处计数器增量似乎在错误的位置。

考虑因素: 周末:周六和周日 需要排除的天数:给定节假日日历中的周六、周日和节假日:

假期table创作:

CREATE TABLE HOLIDAY_TAB
(
   HOL_DATE      DATE,
   DESCRIPTION   VARCHAR2 (100) DEFAULT NULL
);
insert into  HOLIDAY_TAB values (TO_DATE ('26-Jan-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('29-Mar-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('14-Apr-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('01-May-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('02-Jun-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('26-Jun-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('15-Aug-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('25-Aug-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('28-Sep-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('02-Oct-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('19-Oct-2017', 'DD-MON-YYYY'),NULL);
insert into  HOLIDAY_TAB values (TO_DATE ('25-Dec-2017', 'DD-MON-YYYY'),NULL);

commit;

写入捕获假期的函数:

函数:

CREATE OR REPLACE FUNCTION CSE.F_HOL_CHECK_ABC (i_hol_date DATE)
   RETURN DATE
AS
   valid_working_day   DATE := i_hol_date;

   day_C               holiday_nvs%ROWTYPE;

   CURSOR c_hol
   IS
      SELECT *
        FROM HOLIDAY_TAB
       WHERE TRUNC (hol_date) = TRUNC (i_hol_date);

   CURSOR c_hol_24
   IS
      SELECT *
        FROM HOLIDAY_TAB
       WHERE TRUNC (hol_date) = TRUNC (i_hol_date + 3);

   CURSOR c_hol_48
   IS
      SELECT *
        FROM HOLIDAY_NVS
       WHERE TRUNC (hol_date) = TRUNC (i_hol_date + 2);
BEGIN
--   FOR rec24 IN c_hol_24
--   LOOP
--      IF (rec24.hol_date IS NOT NULL)
--      THEN
--         valid_working_day := i_hol_date - 1;
--      END IF;
--   END LOOP;

   OPEN c_hol;

   FETCH c_hol INTO day_C;

   IF c_hol%FOUND
   THEN
      SELECT DECODE (TO_CHAR (i_hol_date - 1, 'D'),
                     1, i_hol_date - 3,
                     i_hol_date -  )
        INTO valid_working_day
        FROM DUAL;
   END IF;

   CLOSE c_hol;


   RETURN (valid_working_day);
END;
/

不确定功能是否正确。但是有一种奇怪的情况,即与日期文字相比,我的查询在尝试使用 SYSDATE 时没有给出相同的结果。

运行 1 手动设置日期:

SELECT TRUNC (
          DECODE (
             TO_CHAR (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY'), 'D'),
             2, F_HOL_CHECK_ABC (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY') - 4),
             DECODE (
                TO_CHAR (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY'), 'D'),
                3, F_HOL_CHECK_ABC (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY') - 4),
                F_HOL_CHECK_ABC (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY') - 2))))
          AS "48HOURS",
       TRUNC (
          DECODE (
             TO_CHAR (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY'), 'D'),
             2, F_HOL_CHECK_ABC (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY') - 3),
             F_HOL_CHECK_ABC (TO_DATE ('03-MAY-2017', 'DD-MON-YYYY') - 1)))
          AS "24HOURS"
  FROM DUAL;

运行 2 系统日期:

SELECT TRUNC (
          DECODE (
             TO_CHAR (SYSDATE, 'D'),
             2, F_HOL_CHECK_ABC (SYSDATE - 4),
             DECODE (TO_CHAR (SYSDATE, 'D'),
                     3, F_HOL_CHECK_ABC (SYSDATE - 4),
                     F_HOL_CHECK_ABC (SYSDATE -2))))      as "48 hour"                          ,

                                                     TRUNC (
                                                        DECODE (
                                                           TO_CHAR (SYSDATE,
                                                                    'D'),
                                                           2, F_HOL_CHECK_ABC (
                                                                 SYSDATE - 3),
                                                           F_HOL_CHECK_ABC (
                                                              SYSDATE -1))) as "24 hour" from dual;

非常感谢您提供这方面的帮助。我所需要的只是跳过节假日、星期日和星期六,即;非工作时间,给我每天 48 小时工作时间和 24 小时工作时间

这是使用计数器对代码 2 进行的另一次尝试:

CREATE OR REPLACE FUNCTION CSE.F_HOL_CHECK_S_NS (i_hol_date    DATE,
                                                 i_S_NS        NUMBER)
   RETURN DATE
AS
   valid_working_day   DATE := i_hol_date;
   counter             NUMBER := 0;
   day_number          NUMBER := 0;
   hol_count           NUMBER := 0;
   day_C               holiday_nvs%ROWTYPE;

   CURSOR c_hol (hol_date_c DATE)
   IS
      SELECT *
        FROM HOLIDAY_TAB
       WHERE TRUNC (hol_date) = TRUNC (TO_DATE (hol_date_c, 'DD-MON-YYYY'));
BEGIN
   IF i_S_NS = 0
   THEN
      LOOP
         IF c_hol%ISOPEN
         THEN
            CLOSE c_hol;
         END IF;

         OPEN c_hol (valid_working_day);


         IF c_hol%FOUND
         THEN
            valid_working_day := valid_working_day - 1;

            SELECT TO_CHAR (TO_DATE (valid_working_day, 'DD-MON-YYYY'), 'D')
              INTO day_number
              FROM DUAL;

            SELECT COUNT (*)
              INTO hol_count
              FROM HOLIDAY_TAB
             WHERE TRUNC (hol_date) =
                      TRUNC (TO_DATE (valid_working_day, 'DD-MON-YYYY'));

            --valid_working_day:=valid_working_day-1;

            --            SELECT DECODE (TO_CHAR (valid_working_day - 1, 'D'),
            --                           1, valid_working_day - 3,
            --                           valid_working_day - 1)
            --              INTO valid_working_day

            --               FROM DUAL;


            IF (hol_count > 0)
            THEN
               valid_working_day := valid_working_day - 1;
            --  counter := counter + 1;
            ELSIF (day_number = 1 OR day_number = 7)
            THEN
               valid_working_day := valid_working_day - 1;
            END IF;
         ELSIF (   TO_CHAR (TO_DATE (valid_working_day, 'DD-MON-YYYY'), 'D') =
                      1
                OR TO_CHAR (TO_DATE (valid_working_day, 'DD-MON-YYYY'), 'D') =
                      7)
         THEN
            valid_working_day := valid_working_day - 1;
         ELSE
            counter := counter + 1;
            valid_working_day := valid_working_day - 1;
         END IF;

         EXIT WHEN counter >= 3;
      END LOOP;
   --elsif (i_S_NS <> 0) then
   --null;
   END IF;
--valid_working_day := valid_working_day - 1;

   RETURN (valid_working_day);
END;

这是一个纯粹的 SQL 答案。诀窍是生成一系列涵盖所有可能性的先前日期。在我所知道的国家/地区,连续的假期不超过两个 public(英国的圣诞节和复活节,苏格兰的除夕节)。也考虑到周末,这意味着最多可以排除四天的时间。

正如评论者指出的那样,在其他国家/地区可能会有更长的 public 假期,因此您可能需要相应地调整偏移量。

无论如何,有两天的目标,我们需要一个可以追溯到六天的范围(加上运气)。这将为我们提供目标日期前 7 天的结果集和 :

select (tgt_date - 7) + (level-1)
from dual
connect by level <= 7

现在我们准备好了。我们可以使用带有 'IW' 日期掩码的技巧来将星期几确定为数字,并以文化中立的方式排除星期六和星期日。我们可以左加入 holiday_tab 以排除 public 假期。然后我们对剩下的和 select 最近的两个日期进行排名:

SQL> with hdr as (
  2               select dr.dt
  3                      ,  case
  4                            when (1 + dt - trunc(dr.dt, 'IW') in (6,7) then 1
  5                            when h.hol_date is not null then 1
  6                            else 0
  7                         end as hol
  8              from ( select trunc(date '2017-05-02' - 7) + (level-1) as dt
  9                     from dual
 10                     connect by level <= 7
 11                   ) dr
 12            left join holiday_tab h
 13            on h.hol_date = dr.dt
 14              )
 15     , rhdr as (
 16         select hdr.dt
 17                    , row_number() over (order by hdr.dt desc) rn
 18             from hdr
 19             where hdr.hol = 0
 20             )
 21  select rhdr.dt
 22         , decode( rhdr.rn, 1, '24hr', '48hr') as cat
 23         , to_char(rhdr.dt, 'DY') as dy
 24  from rhdr
 25  where rn <= 2;

DT        CAT  DY
--------- ---- ------------
28-APR-17 24hr FRI
27-APR-17 48hr THU

SQL>

鉴于昨天 02-MAY-2017 作为目标日期,这将跳过星期一(五一假期)和周末来识别前两个工作日。


如果你需要一个函数,你可以这样做:

create or replace type dt_nt as table of date;

create or replace function prior_working_days
    ( p_target_date in date
      , p_no_of_days in number := 2)
    return dt_nt
is
    return_value dt_nt;
    offset pls_integer := (p_no_of_days+4+1);
begin
    with hdr as (
                select dr.dt
                        ,  case 
                               when to_char(1 + dt - trunc(dr.dt, 'IW') in (6,7) then 1 
                               when h.hol_date is not null then 1
                               else 0
                            end as hol
                from ( select (trunc(p_target_date) - offset) + (level-1) as dt
                         from dual
                         connect by level <= offset
                        ) dr
                     left join holiday_tab h
                     on h.hol_date = dr.dt
                )
        , rhdr as (
            select hdr.dt
                   , row_number() over (order by hdr.dt desc) rn
            from hdr
            where hdr.hol = 0
            )
    select rhdr.dt 
           bulk collect into return_value
    from rhdr   
    where rn <= p_no_of_days;
    return return_value;
end prior_working_days;
/

returns SQL table 个日期:

SQL> select * from table( prior_working_days(sysdate));

COLUMN_VA
---------
02-MAY-17
28-APR-17

SQL> 

我的建议是这样的函数:

CREATE OR REPLACE FUNCTION F_HOL_CHECK_S_NS (i_hol_date DATE, i_S_NS NUMBER) RETURN DATE AS
    TYPE DATE_TABLE_TYPE is TABLE OF DATE;
    Holidays DATE_TABLE_TYPE;

    the_date DATE := i_hol_date;
    duration INTEGER := 0;
BEGIN

    ID i_hol_date IS NULL OR i_S_NS IS NULL THEN
        -- Avoid infinite loop
        RETURN NULL;
    END IF;

    -- Just for performance reason
    SELECT HOL_DATE
    BULK COLLECT INTO Holidays
    FROM HOLIDAY_TAB
    WHERE HOL_DATE < i_hol_date;

    LOOP
        the_date := the_date - 1;
        IF TO_CHAR(the_date, 'fmDy', 'NLS_DATE_LANGUAGE = american') NOT IN ('Sat', 'Sun') AND TRUNC(the_date) NOT MEMBER OF Holidays THEN
            duration := duration + 24;
        END IF;
        EXIT WHEN duration >= i_S_NS;
    END LOOP;
    RETURN the_date;
END;    


SELECT F_HOL_CHECK_S_NS(DATE '2017-05-02', 24) FROM dual;
SELECT F_HOL_CHECK_S_NS(DATE '2017-05-02', 48) FROM dual;
SELECT F_HOL_CHECK_S_NS(SYSDATE, 24) FROM dual;
SELECT F_HOL_CHECK_S_NS(SYSDATE, 48) FROM dual;

您可以在 SQL 中完成所有操作:

WITH dates ( dt, lvl ) AS (
  SELECT CAST( TRUNC( :your_date ) AS DATE ), 0 FROM DUAL
UNION ALL
  SELECT CAST( dt - INTERVAL '1' DAY AS DATE ),
         CASE
           WHEN ( dt - INTERVAL '1' DAY ) - TRUNC( dt - INTERVAL '1' DAY, 'IW' ) >= 5
           OR   hol_date IS NOT NULL
           THEN lvl
           ELSE lvl + 1
         END
  FROM   dates d
         LEFT OUTER JOIN
         holidays h
         ON ( d.dt - INTERVAL '1' DAY = h.hol_date )
  WHERE  lvl < 2
)
SELECT *
FROM   dates
PIVOT  ( MAX( dt ) FOR lvl IN ( 1 AS DATE24, 2 AS DATE48 ) );

(注意:使用 CAST 不是必需的,但没有我得到 ORA-01790: expression must have same datatype as corresponding expression

或者,作为函数:

CREATE OR REPLACE FUNCTION F_HOL_CHECK_S_NS (
  i_hol_date DATE,
  i_S_NS     NUMBER
) RETURN DATE
AS
  p_date DATE;
BEGIN
  WITH dates ( dt, lvl ) AS (
    SELECT CAST( TRUNC( i_hol_date ) AS DATE ), 0 FROM DUAL
  UNION ALL
    SELECT CAST( dt - INTERVAL '1' DAY AS DATE ),
           CASE
             WHEN ( dt - INTERVAL '1' DAY ) - TRUNC( dt - INTERVAL '1' DAY, 'IW' ) >= 5
             OR   hol_date IS NOT NULL
             THEN lvl
             ELSE lvl + 1
           END
    FROM   dates d
           LEFT OUTER JOIN
           holidays h
           ON ( d.dt - INTERVAL '1' DAY = h.hol_date )
    WHERE  lvl < i_s_ns
  )
  SELECT dt
  INTO   p_date
  FROM   dates
  WHERE  lvl = i_s_ns;

  RETURN p_date;
END;
/