在Oracle SQL中计算工作日(无函数或过程)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Oracle SQL中计算工作日(无函数或过程)相关的知识,希望对你有一定的参考价值。
我正在尝试计算Oracle select中两个日期之间的工作日。当我的计算给出给定日期的大多数结果时,我得到了这一点(我将它与excel中的NETWORKDAYS进行比较),但有时它在2天到-2天之间变化 - 我不知道为什么......
这是我的代码:
SELECT
((to_char(CompleteDate,'J') - to_char(InstallDate,'J'))+1) - (((to_char(CompleteDate,'WW')+ (52 * ((to_char(CompleteDate,'YYYY') - to_char(InstallDate,'YYYY'))))) - to_char(InstallDate,'WW'))*2) as BusinessDays
FROM TABLE
谢谢!
解决方案,最后:
SELECT OrderNumber, InstallDate, CompleteDate,
(TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 -
((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) as BusinessDays
FROM Orders
ORDER BY OrderNumber;
感谢您的所有回复!
干得好...
- 首先检查您在假期表中获得的天数,不包括周末天数。
- 获取2个日期之间的工作日(MON到FRI),然后减去假期。
create or replace FUNCTION calculate_business_days (p_start_date IN DATE, p_end_date IN DATE) RETURN NUMBER IS v_holidays NUMBER; v_start_date DATE := TRUNC (p_start_date); v_end_date DATE := TRUNC (p_end_date); BEGIN IF v_end_date >= v_start_date THEN SELECT COUNT (*) INTO v_holidays FROM holidays WHERE day BETWEEN v_start_date AND v_end_date AND day NOT IN ( SELECT hol.day FROM holidays hol WHERE MOD(TO_CHAR(hol.day, 'J'), 7) + 1 IN (6, 7) ); RETURN GREATEST (NEXT_DAY (v_start_date, 'MON') - v_start_date - 2, 0) + ( ( NEXT_DAY (v_end_date, 'MON') - NEXT_DAY (v_start_date, 'MON') ) / 7 ) * 5 - GREATEST (NEXT_DAY (v_end_date, 'MON') - v_end_date - 3, 0) - v_holidays; ELSE RETURN NULL; END IF; END calculate_business_days;
之后你可以测试出来,比如:
select
calculate_business_days('21-AUG-2013','28-AUG-2013') as business_days
from dual;
还有另一种更简单的方法,使用connect by和dual ...
with t as (select to_date('30-sep-2013') end_date, trunc(sysdate) start_date from dual)select count(1) from dual, t where to_char(t.start_date + level, 'D') not in (1,7) connect by t.start_date + level <= t.end_date;
通过connect获取从start_date到end_date的所有日期。然后,您可以排除不需要的日期,只计算所需的日期。
这将返回工作日:
(CompleteDate-InstallDate)-2*FLOOR((CompleteDate-InstallDate)/7)-
DECODE(SIGN(TO_CHAR(CompleteDate,'D')-
TO_CHAR(InstallDate,'D')),-1,2,0)+DECODE(TO_CHAR(CompleteDate,'D'),7,1,0)-
DECODE(TO_CHAR(InstallDate,'D'),7,1,0) as BusinessDays,
我考虑了上面讨论的所有不同的方法,并提出了一个简单的查询,它给出了两个日期之间一年中每个月的工作日数:
WITH test_data AS
(
SELECT TO_DATE('01-JAN-14') AS start_date,
TO_DATE('31-DEC-14') AS end_date
FROM dual
),
all_dates AS
(
SELECT td.start_date, td.end_date, td.start_date + LEVEL-1 as week_day
FROM test_data td
CONNECT BY td.start_date + LEVEL-1 <= td.end_date)
SELECT TO_CHAR(week_day, 'MON'), COUNT(*)
FROM all_dates
WHERE to_char(week_day, 'dy', 'nls_date_language=AMERICAN') NOT IN ('sun' , 'sat')
GROUP BY TO_CHAR(week_day, 'MON');
请随意根据需要修改查询。
试试这个:
with holidays as
(
select d from (
select minDate + level -1 d
from (select min(submitDate) minDate, max (completeDate) maxDate
from t)
connect by level <= maxDate - mindate + 1)
where to_char(d, 'dy', 'nls_date_language=AMERICAN') not in ('sun' , 'sat')
)
select t.OrderNo, t.submitDate, t.completeDate, count(*) businessDays
from t join holidays h on h.d between t.submitDate and t.completeDate
group by t.OrderNo, t.submitDate, t.completeDate
order by orderno
我看到标记的最终解决方案总是不正确。假设,InstallDate是本月的第1天(如果是星期六),则CompleteDate是月份的第16天(如果是星期日)
在这种情况下,实际营业日是10,但标记的查询结果将给出答案为12.因此,我们也必须处理这种类型的案例,我使用过
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SUN' THEN 2 ELSE 0 END
行来处理它。
SELECT OrderNumber, InstallDate, CompleteDate,
(TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 -
((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SUN' THEN 2 ELSE 0 END)as BusinessDays
FROM Orders
ORDER BY OrderNumber;
我将我的示例更改为更具可读性并返回总线计数。几天之间。我不知道为什么你需要'J'-朱利安格式。所需要的只是开始/安装和结束/完成日期。您将使用此日期在两个日期之间获得正确的天数。用你的日期替换我的日期,如果需要,添加NLS ...:
SELECT Count(*) BusDaysBtwn
FROM
(
SELECT TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1 InstallDate -- MON or any other day
, TO_DATE('2013-02-25', 'YYYY-MM-DD') CompleteDate -- MON or any other day
, TO_CHAR(TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1, 'DY') InstallDay -- day of week
FROM dual
CONNECT BY LEVEL <= (TO_DATE('2013-02-25', 'YYYY-MM-DD') - TO_DATE('2013-02-18', 'YYYY-MM-DD')) -- end_date - start_date
)
WHERE InstallDay NOT IN ('SAT', 'SUN')
/
SQL> 5
已接受的解决方案非常接近但在某些情况下似乎是错误的(例如,2015年2月1日至2015年2月28日或2015年5月1日至2015年5月31日)。这是一个精致的版本......
end_date-begin_date+1 /* total days */
- TRUNC(2*(end_date-begin_date+1)/7) /* weekend days in whole weeks */
- (CASE
WHEN TO_CHAR(begin_date,'D') = 1 AND REMAINDER(end_date-begin_date+1,7) > 0 THEN 1
WHEN TO_CHAR(begin_date,'D') = 8 - REMAINDER(end_date-begin_date+1,7) THEN 1
WHEN TO_CHAR(begin_date,'D') > 8 - REMAINDER(end_date-begin_date+1,7) THEN 2
ELSE 0
END) /* weekend days in partial week */
AS business_days
处理7(整周)倍数的部分是好的。但是,在考虑部分周部分时,它取决于星期偏移量和部分部分中的天数,根据以下矩阵...
654321
1N 111111
2M 100000
3T 210000
4W 221000
5R 222100
6F 222210
7S 222221
要删除星期日和星期六,你可以使用它
SELECT Base_DateDiff
- (floor((Base_DateDiff + 0 + Start_WeekDay) / 7))
- (floor((Base_DateDiff + 1 + Start_WeekDay) / 7))
FROM (SELECT 1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW') Start_WeekDay
, CompleteDate - InstallDate + 1 Base_DateDiff
FROM TABLE) a
Base_DateDiff
计算两个日期之间的天数
(floor((Base_DateDiff + 0 + Start_WeekDay) / 7))
计算星期日的数量
(floor((Base_DateDiff + 1 + Start_WeekDay) / 7))
计算星期六的数量
1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW')
星期一获得1分,星期日获得7分
此查询可用于从给定日期开始N天(仅限工作日)
例如,从2017-05-17返回15天:
select date_point, closest_saturday - (15 - offset + floor((15 - offset) / 6) * 2) from(
select date_point,
closest_saturday,
(case
when weekday_num > 1 then
weekday_num - 2
else
0
end) offset
from (
select to_date('2017-05-17', 'yyyy-mm-dd') date_point,
to_date('2017-05-17', 'yyyy-mm-dd') - to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') closest_saturday,
to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') weekday_num
from dual
))
一些简短的解释:假设我们想要从给定日期起N天后退 - 找到小于或等于给定日期的最接近的星期六。 - 从最近的星期六开始,回到病房(N - 偏移)
以上是关于在Oracle SQL中计算工作日(无函数或过程)的主要内容,如果未能解决你的问题,请参考以下文章