计算工作日(多种算法)

[复制链接]
查看11 | 回复9 | 2014-7-15 06:00:13 | 显示全部楼层 |阅读模式
要求:有两个列(时间格式),求出所有这两个列中的每一条的工作日总数(不含周六、不含周日,含开始时间,不含结束时间)先贴sql代码,一共五个算法(自测五个算法均无问题),其中第一种算法引用了一个function:
select t.created,
t.last_upd,
TO_CHAR(T.CREATED, 'D') 开始时间,
TO_CHAR(T.LAST_UPD, 'D') 结束时间,
cacl_workdays(t.created, t.last_upd) 第一种,
FLOOR((T.LAST_UPD - T.CREATED) / 7) * 5 + CASE
WHEN TO_CHAR(T.LAST_UPD, 'D') > TO_CHAR(T.CREATED, 'D') THEN --同一周(包含首天)

TO_CHAR(T.LAST_UPD, 'D') -

decode(TO_CHAR(T.CREATED, 'D'), 1, 2, TO_CHAR(T.CREATED, 'D'))
WHEN TO_CHAR(T.LAST_UPD, 'D')

TRUNC(t.last_upd - t.created) + 1 THEN --

0
ELSE

TRUNC((TRUNC(t.last_upd - t.created) -

(8 - TO_NUMBER(TO_CHAR(t.created, 'D')))) / 7) + 1
END) --周六
+ (CASE --
WHEN MOD(8 - TO_CHAR(t.created, 'D'), 7) >

TRUNC(t.last_upd - t.created) - 1 THEN

0
ELSE

TRUNC((TRUNC(t.last_upd - t.created) -

(MOD(8 - TO_CHAR(t.created, 'D'), 7) + 1)) / 7) + 1
END))) AS 第三种,
trunc((T.LAST_UPD - T.CREATED) / 7) * 5 +
nvl(length(replace(substr('01111100111110',

to_char(T.CREATED, 'd'),

mod(T.LAST_UPD - T.CREATED, 7)),

'0',

'')),
0) 第四种,

((TO_NUMBER(TRUNC(T.LAST_UPD, 'D') - TRUNC(T.CREATED + 6, 'D'))) / 7 * 5) + --隔板作用(中间段)
MOD(7 - TO_NUMBER(TO_CHAR(T.CREATED, 'D')), 6) + --前一段
greatest(TO_NUMBER(TO_CHAR(T.LAST_UPD, 'D')) - 2, 0) as 第五种 --后一段
from TEST1 t
复制代码再加一个functions的代码:
create or replace function cacl_workdays(statedate date,enddate date) return int is
Resultint ;
begin
select count(1)
into Result
from (select statedate + rownum-1 as dd

from dual
connect by rownum1
and to_char(t.dd,'d')7;
return(Result);
end cacl_workdays;
复制代码

回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
再来说算法:
第一种,也是最常见的一种,使用connect 生成一段连续的日期(开始时间至结束时间前一天),然后统计在排除掉周六和周日,再计算count,,但是这个方法有着致命的缺点:效率很低,在结束时间大雨开始时间很大时,效率极其低下
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
第二种,也是比较容易想到的一种方法:
1、先计算结束时间和开始时间内的周数,/7*5得到完整周的工作日数
2、然后判断结束时间和开始时间是否同一周内在进行计算。同一周的计算重点是:TO_CHAR(T.LAST_UPD, 'D') -decode(TO_CHAR(T.CREATED, 'D'), 1, 2, TO_CHAR(T.CREATED, 'D'),至于那个decode内的1,2怎么得来的,是穷举找规律得到的;不同周的计算重点是: decode(TO_CHAR(T.LAST_UPD, 'D'), 1, 6, 5) ,至于 1,6,5怎么得来的,还是穷举找规律
此方法有些穷举的味道在里面,
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
第三种,我从网上盗取的,还没怎么看懂,我猜测大概意思是:
1、结束时间-开始时间= 总天数
2、总天数- 周六的个数-周日的个数=工作日
恳请论坛大神解释哦,我看了很久都没怎么看懂。。。。。。。还要提高sql水平,看不懂
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
第四种算法,这个算法很巧,也没多少判断,在这五个算法中,最喜欢这个(简单明了,效率又高)
1、关键点:字符串“01111100111110”是代表两周时间(周日开始),相当于就是砍掉中间的完整周,再把开始时间到结束时间连起来
2、substr从开始时间(周几)取值,取多少个(mod对7取余),再把字符串的0去掉求长度就可以了
大爱这种做法,很聪明有木有?
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
第五种算法,也比较巧,我也蛮喜欢,有点类似于高中学的排列组合中的 挡板算法
1、开始时间推到本周最后一天(也就是 TRUNC(T.CREATED + 6, 'D')),结束时间提前到本周第一天( TRUNC(T.LAST_UPD, 'D')),然后在算这个相当完整周(周日到周日)的工作日,得到A
2、计算开始时间到本周最后一天的 工作日,得到B;计算结束时间的本周第一天到结束时间的工作日
3、三个时间段的工作日累加 ,得到结果
这个方法也很巧,也很喜欢这种做法,大爱
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
不要用函数,每行都会调用一次.
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
构建一个工作日日历表不是最好的办法么, 还可以设置法定节假日


回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
注意to_char(日期,'D')的结果不是确定的,受nls_territory设置的影响,比如英国和美国返回的就不一样。
第四种恰恰是不好的,如果日期间隔很大,得浪费多少内存来放这个字符串?其实无非就是首位两周要处理一下,就是第五种的思路,这是最简单明了的。
回复

使用道具 举报

千问 | 2014-7-15 06:00:13 | 显示全部楼层
chengccy2010 发表于 2016-10-11 18:57
构建一个工作日日历表不是最好的办法么, 还可以设置法定节假日

当然这个更好,我只是就事论事,只是单纯计算 工作日
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行