转载

Session重叠问题学习(八)--该问题第六次优化和Oracle版本

已经说好不玩了.还是不能罢手.

之前极致优化的方法,是先计算最小区间,再计算最小区间上的用户数量.

其实可以一边计算最小区间,同时计算最小区间上的用户数量.

  1. DELIMITER $$  
  2.   
  3. CREATE DEFINER=`root`@`localhost` PROCEDURE `p`()  
  4. BEGIN          
  5.     drop table if exists t1;          
  6.     drop table if exists tmp_time_point;          
  7.     drop table if exists tmp_s;      
  8.     CREATE temporary TABLE `t1` (          
  9.       `roomid` int(11) NOT NULL DEFAULT '0',          
  10.       `userid` bigint(20) NOT NULL DEFAULT '0',          
  11.       `s` timestamp,          
  12.       `e` timestamp,      
  13.        primary key(roomid,userid,s,e)      
  14.     ) ENGINE=memory;          
  15.         
  16.   
  17.     create temporary table tmp_time_point(          
  18.             roomid bigint,          
  19.             timepoint timestamp,          
  20.             type smallint,        
  21.             key(roomid,timepoint)          
  22.     ) engine=memory;          
  23.           
  24.     create temporary table tmp_s(      
  25.         roomid bigint,      
  26.         userid bigint,      
  27.         s timestamp,      
  28.         e timestamp,      
  29.         i int      
  30.     ) engine=memory;      
  31.           
  32. SET @A=0;          
  33. SET @B=0;          
  34.       
  35. insert into tmp_s      
  36.     SELECT x.roomid,x.userid,s,e,datediff(e,s)+1 i       
  37.     FROM         
  38.     (        
  39.         (        
  40.             SELECT @B:=@B+1 AS id,roomid,userid,s          
  41.             FROM (          
  42.                 SELECT DISTINCT roomid, userid, roomstart AS s              
  43.                 FROM u_room_log a              
  44.                 WHERE NOT EXISTS (SELECT *              
  45.                     FROM u_room_log b              
  46.                     WHERE a.roomid = b.roomid              
  47.                         AND a.userid = b.userid              
  48.                         AND a.roomstart > b.roomstart              
  49.                         AND a.roomstart <= b.roomend)        
  50.             ) AS p        
  51.         ) AS x,          
  52.         (        
  53.             SELECT @A:=@A+1 AS id,roomid,userid,e          
  54.             FROM         
  55.             (          
  56.                 SELECT DISTINCT roomid, userid, roomend AS e              
  57.                 FROM u_room_log a              
  58.                 WHERE NOT EXISTS (SELECT *              
  59.                     FROM u_room_log b              
  60.                     WHERE a.roomid = b.roomid              
  61.                         AND a.userid = b.userid              
  62.                         AND a.roomend >= b.roomstart              
  63.                         AND a.roomend < b.roomend)          
  64.             ) AS o        
  65.         ) AS y          
  66.     )         
  67.     WHERE x.id = y.id AND x.roomid = y.roomid AND x.userid = y.userid   ;         
  68.       
  69. select max(i) into @c from tmp_s;      
  70.           
  71. insert ignore into t1(roomid,userid,s,e)        
  72. select             
  73. roomid,  userid,            
  74. if(date(s)!=date(e) and id>1,date(s+interval id-1 day),s) s,            
  75. if(date(s+interval id-1 day)=date(e) ,e,date_format(s+interval id-1 day,'%Y-%m-%d 23:59:59')) e            
  76. from tmp_s t1 STRAIGHT_JOIN          
  77. nums on(nums.id<=t1.i)      
  78. where nums.id<=@c      
  79.          
  80. ;            
  81.         
  82.     -- 开始点+1,结束点-1    
  83.     insert into tmp_time_point(roomid,timepoint,type) select roomid,s,1 from t1;        
  84.     insert into tmp_time_point(roomid,timepoint,type) select roomid,e,-1 from t1;     
  85.     
  86.         select  roomid,date(s) dt,round(sum(timestampdiff(second,date_format(s,'%Y-%m-%d %H:%i:%s'),date_format(e,'%Y-%m-%d %H:%i:%s')))/60) ts,max(rn) c from (          
  87.                 select           
  88.                 if(@roomid=roomid,@d,'')  as s,@d:=str_to_date(timepoint,'%Y-%m-%d %H:%i:%s.%f'),@roomid:=roomid,p.roomid,str_to_date(timepoint,'%Y-%m-%d %H:%i:%s.%f') e  ,rn        
  89.                 from   
  90.                 (  
  91.                     select round(case when @roomid=roomid then @rn:=@rn+prevType when @roomid:=roomid then @rn:=prevType end) rn,b.prevType,roomid,timepoint,type  from (  
  92.                         select if(@roomid=roomid,@type,0) prevType ,case when @roomid=roomid then @type:=type when @roomid:=roomid then @type:=1 end,a.roomid,timepoint,type from (  
  93.                             select * from (    select roomid,timepoint,sum(type) type from tmp_time_point group by  roomid,timepoint) tmp_time_point,(select @roomid:=-1,@rn:=0,@type:=0) vars order by roomid ,timepoint  
  94.                         ) a  
  95.                     ) b order by roomid ,timepoint   
  96.                 )p,(select @d:='',@roomid:=-1) vars          
  97.                 order by roomid,timepoint          
  98.         ) v4 where s!='' and date(s)=date(e) and rn>=2      
  99.         group by roomid,date(s);         
  100.         
  101. END  


到了这个MySQL的版本,确实已经非常的晦涩难懂了.

由于MySQL没有提供开窗函数,这种复杂计算非常的吃亏.

这个最后的版本,执行时间在 889毫秒到921毫秒之间. 
MySQL 版本 最后还能优化小100毫秒,非常的不容易.

这个版本过于晦涩,我用同样的思路,写了一个Oracle版本的.
性能好,可读性还强.

  1. with c1 as  -- 合并同一房间同一用户的重叠时间段,用于统计峰值人数  
  2. (  
  3.    select distinct roomid,userid,min(s) s,max(e) e   
  4.      from (select roomid,userid,s,e,  
  5.                   sum(broken) over (partition by roomid, userid order by s,e) flag  
  6.              from (select t.*,  
  7.                           (case when s <= max(e) over (partition by roomid, userid order by s,e rows between unbounded preceding and 1 preceding) then 0  
  8.                            else 1  
  9.                             endas broken  
  10.                      from (select roomid,userid,roomstart s,roomend e from u_room_log ) t  
  11.                    ) t  
  12.            ) t    
  13.     group by roomid,userid,flag  
  14. ),  
  15. c2 as  -- 拆分跨天的时间段  
  16. (  
  17.    select *   
  18.      from (select roomid,userid,s,e   
  19.              from c1  
  20.             where trunc(s) = trunc(e)  -- 不跨天  
  21.             union all  
  22.            select roomid,userid,  
  23.                   case when id = 1 then s else trunc(s)+id-1 end s,  
  24.                   case when id = m2 then e else (trunc(s)+id) -(1/24/60/60)  end e       
  25.              from (select roomid,userid,s,e,id,  
  26.                           max(id) over (partition by roomid,userid,s) m2  
  27.                      from c1,nums  
  28.                     where trunc(s) <> trunc(e) -- 跨天  
  29.                       and id <= trunc(e)-trunc(s)+1) t1) t1  
  30. ),  
  31. c3 as -- 在计算最小范围的同时,计算区间用户数  
  32. (  
  33.   
  34.       select roomid,ts endtime,sum(prevType) over(partition by roomid order by ts) rn,   
  35.       lag(ts) over (partition by roomid order by ts) starttime  
  36.       from (  
  37.         select a.*,nvl(lag(type) over (partition by roomid order by ts),0) prevType  
  38.         from (  
  39.           select   
  40.           roomid,ts,sum(type) type  
  41.           from (  
  42.               select roomid,e ts, -1 type  
  43.               from c2  
  44.               union all  
  45.               select roomid,s ts, 1 type  
  46.               from c2  
  47.           ) group by roomid,ts  
  48.         ) a  
  49.       ) c  
  50. )  
  51. select roomid,to_char(dt,'yyyy-mm-dd HH24:mi:ss') dt,round(sum(dur)/60) ts,max(rn) c from (  
  52.   select roomid,trunc(starttime) dt,(endtime-starttime)*3600*24 dur,rn   
  53.   from c3 where rn>=2 and trunc(endtime)=trunc(starttime) and starttime is not null   
  54.   order by roomid,endtime  
  55. )   
  56. group by roomid,dt  
  57. order by roomid,dt; 

不像MySQL,需要考虑所有的细节.这个同样算法的Oracle版本,随随便便就能跑到213毫秒.


正文到此结束
Loading...