要求找到某个模块的第48和第24个工作小时。
要求:
假设我将2nd May
作为参数传递给函数,27th April
的输出应为48 hours
而28th APRIL
的输出应为24 hours
五月是假期,四月二十九日和三十日在星期六和星期日下降)
问题在于连续两个假期。例如,要创建dummy
数据,我们会将5月2日作为假日插入,并在3rd May
上运行代码,该代码应为27th April
和48 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;
答案 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;