問題是計算通道的總開放時長,只要有任意一個終端開放通道就算開放,難點在於各種終端開放時間重疊包含。
三種思路:
1、先取排序後的第一條數據的時間段爲基準,然後兩兩比較,累加時間。
2、把兩兩時間串起來,有交集的時間段,直接取最小時間至最大時間,沒交集的減去中間不連續的部分。
還有一種思路是求出每個時間段的值,然後減去重疊的時間段。這種過於複雜不考慮,因爲重疊的時間段肯定不止重複一次。
第一種實現會在一些嵌套包含的情況下變得複雜,所以無奈只能先過濾那些完全被包含的記錄。
第二種實現目前來看代碼簡潔,思路清晰,而且使用存儲過程實現,效率較高。
一、分析
二、代碼實現
drop table xcp;
create table xcp(terminal varchar2(2),channel varchar2(2),begin_time date,end_time date);
--問題1、計算總時長,以下爲測試數據
insert into xcp values('1','A1',to_date('20200317 01:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 06:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 01:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 06:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 01:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 08:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 02:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 07:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 03:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 07:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 05:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 09:00:00','yyyymmdd hh24:mi:ss '));
insert into xcp values('3','A1',to_date('20200317 09:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 11:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('3','A1',to_date('20200317 12:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 13:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('2','A1',to_date('20200317 14:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 19:00:00','yyyymmdd hh24:mi:ss '));
insert into xcp values('3','A1',to_date('20200317 16:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 19:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('3','A1',to_date('20200317 18:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 19:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('3','A1',to_date('20200317 18:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 21:00:00','yyyymmdd hh24:mi:ss'));
commit;
select * from xcp order by begin_time;
--計算總時長,方法一
select sum(greatest(y.acc, 0) - greatest(y.feature2, 0)) total_time_hours
--select y.*, greatest(y.acc, 0) - greatest(y.feature2, 0) total_time_hours
from (select x.*,
(x.end_time - x.pre_end_time) * 24 acc, --累加值
(x.end_time - x.pre_end_time) * 24 feature1, --特徵1,這個值小於0則爲第二類,也就是情形4,可以發現這個特徵的計算方式其實和累加值計算方式一樣,這裏爲了演示,真實場景可以不用計算,爲了忽略情形4,直接使用 greatest(acc,0)
(x.begin_time - x.pre_end_time) * 24 feature2 --特徵2,這個值大於0則爲第三類,也就是情形8,爲了修正,可以在acc的基礎上,減去 greatest(feature2,0)
from (select t.*,
lag(t.end_time, 1, t.begin_time) over(partition by t.channel order by t.begin_time, t.end_time) pre_end_time, --上一條結束時間,注意第一條給的默認值(第一條沒有上一條)
lag(t.begin_time, 1, t.begin_time) over(partition by t.channel order by t.begin_time, t.end_time) pre_start_time --上一條開始時間,注意第一條給的默認值(第一條沒有上一條)
from xcp t
where not exists (select 1
from xcp s
where s.begin_time < t.begin_time
and s.end_time > t.end_time)) x) y;
--計算總時長,優化後的方法二(思路來自基友https://www.cnblogs.com/yongestcat/p/12590154.html)
--第8的特徵:下一條記錄開始時間 大於 截止當前行的最大結束時間;那麼就把這部分時間記下來,最後減掉即可
select (max(end_time) - min(begin_time)) * 24 -
sum(decode(sign(next_begin_time - max_end_time),
1,
(next_begin_time - max_end_time) * 24,
0)) 通道開通時間
from (select a.channel,
a.begin_time,
a.end_time,
max(a.end_time) over(partition by a.channel order by a.begin_time rows between unbounded preceding and current row) max_end_time, --截止當前行的最大結束時間
lead(a.begin_time, 1) over(partition by a.channel order by a.begin_time) next_begin_time --下一條記錄的開始時間
from xcp a) tmp;
--方法二的plsql實現by小金
/*思路:
第一步:兩兩合併,兩條記錄之間的關係只有兩種:有交集 和 無交集
1)對於有交集的:兩兩合併,取min(begin_time),max(end_time)作爲新記錄,
2)對於無交集的:同樣取min(begin_time),max(end_time)作爲新記錄,不過把中間空白部分計入duration_del
第二步:然後將第一步合併的新紀錄和下一條記錄再兩兩合併,以此類推,直至合併完所有記錄
第三步:結果就是 最終合併記錄的 end_time-begin_time-duration_del*/
declare
duration_del number:=0;--存儲無交集的兩兩記錄之間的空白時間
--用於存儲合併後的時間
begin_time_merge date; end_time_merge date;
--用於輸入要查詢的時間段
day1 date:=to_date(20200314,'yyyymmdd');
day2 date:=to_date(20200330,'yyyymmdd');
begin
--初始化時間
begin_time_merge :=day1; end_time_merge:=day1;
for i in (select rownum rnow,aa.* from
(select a.channel,greatest(a.begin_time, day1) begin_time,least(a.end_time,day2) end_time
from xcp a where not (end_time < day1 or begin_time> day2) order by 2)aa
)loop --掃描一次全表
if i.begin_time>end_time_merge then
duration_del:= duration_del+ (i.begin_time-end_time_merge)*24;--空白部分計入duration_del
end if;
end_time_merge := greatest(end_time_merge,i.end_time);
end loop;
dbms_output.put_line((end_time_merge-begin_time_merge)*24-duration_del||'個小時通道開放');
end;
/
--問題2、計算某一天(17號)時長,先構造跨天數據
insert into xcp values('13','A1',to_date('20200314 08:00:00','yyyymmdd hh24:mi:ss'),to_date('20200315 09:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('14','A1',to_date('20200317 08:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 09:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('15','A1',to_date('20200316 03:00:00','yyyymmdd hh24:mi:ss'),to_date('20200317 05:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('16','A1',to_date('20200317 08:00:00','yyyymmdd hh24:mi:ss'),to_date('20200318 10:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('17','A1',to_date('20200316 08:00:00','yyyymmdd hh24:mi:ss'),to_date('20200318 10:00:00','yyyymmdd hh24:mi:ss'));
insert into xcp values('18','A1',to_date('20200320 08:00:00','yyyymmdd hh24:mi:ss'),to_date('20200321 10:00:00','yyyymmdd hh24:mi:ss'));
commit;
--計算17號開放時長(小時)方法一,方法二就是上面方法二的基礎上修改就行了,這裏就不寫了
with tmp as
(select terminal,
channel,
least(greatest(begin_time, to_date('20200317', 'yyyymmdd')),
to_date('20200317', 'yyyymmdd') + 1) begin_time,
greatest(least(end_time, to_date('20200317', 'yyyymmdd') + 1),
to_date('20200317', 'yyyymmdd')) end_time
from xcp)
select sum(greatest(y.acc, 0) - greatest(y.feature2, 0)) total_time_hours
from (select x.*,
(x.end_time - x.pre_end_time) * 24 acc,
(x.begin_time - x.pre_end_time) * 24 feature2
from (select t.*,
lag(t.end_time, 1, t.begin_time) over(partition by t.channel order by t.begin_time, t.end_time) pre_end_time,
lag(t.begin_time, 1, t.begin_time) over(partition by t.channel order by t.begin_time, t.end_time) pre_start_time
from tmp t
where not exists (select 1
from tmp s
where s.begin_time < t.begin_time
and s.end_time > t.end_time)) x) y;