如何找到48个工作小时的日期,不包括星期六,星期日和oracle的假期

时间:2017-05-02 21:15:16

标签: oracle plsql oracle11g date-arithmetic

要求找到某个模块的第48和第24个工作小时。

要求:

假设我将2nd May作为参数传递给函数,27th April的输出应为48 hours28th APRIL的输出应为24 hours五月是假期,四月二十九日和三十日在星期六和星期日下降)

问题在于连续两个假期。例如,要创建dummy数据,我们会将5月2日作为假日插入,并在3rd May上运行代码,该代码应为27th April48 hours检索28th APRIL 24 hours

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

考虑: 周末:周六和周日 需要排除的日期:给定假日日历中的星期六,星期日和假日:

假期表创建:

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;

使用sysdate运行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;

3 个答案:

答案 0 :(得分:2)

您可以在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;
/

答案 1 :(得分:1)

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

正如评论者指出的那样,在其他国家可能会有更长的公众假期,所以你可能需要相应地调整偏差。

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

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

现在我们已经确定了。我们可以使用'IW'日期掩码的技巧来确定星期几作为数字,并以文化中立的方式排除星期六和星期日。我们可以加入holiday_tab以排除公共假期。然后我们对剩下的内容进行排名并选择最近的两个日期:

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>

鉴于截至2017年5月2日至2017年5月为目标日期,周一(五月天假期)和周末将确定前两个工作日。

如果你需要一个功能,你可以这样做:

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;
/

这将返回一个日期的SQL表:

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

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

SQL> 

答案 2 :(得分:1)

我的建议将是这样的功能:

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;