sql時間段取並集、合併

問題是計算通道的總開放時長,只要有任意一個終端開放通道就算開放,難點在於各種終端開放時間重疊包含。

三種思路:

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;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章