MySQL - 自连接优化

时间:2012-01-26 20:59:15

标签: mysql optimization join

我有HomeId的电话事件表。每行都有一个EventId(挂机,摘机,振铃,DTMF等),TimeStamp,Sequence(自动递增)和HomeId。我正在进行查询以查找特定类型的事件(IE入站或出站呼叫)和持续时间。

我曾计划在此表上使用多个自联接来选择通常指示一种类型的事件或另一种事件的事件序列。 EG入站呼叫将是一段不活动的时间,之后没有DTMF,然后是振铃和来电显示(可能)然后是摘机。我会发现下一个挂机,因此有持续时间。

我的表由HomeId,EventId和Sequence索引,有~60K记录。当我对我的查询进行“解释”时,它会显示索引,而行数为75,75,1,1,748。看起来很可行。但是,当我运行查询时,它花了超过10分钟(此时MySQL查询浏览器超时)。

查询外拨电话:

select pe0.HomeId, pe1.Stamp, pe1.mSec, timediff( pe4.Stamp, pe0.Stamp ) from Phone_Events pe0 
join Phone_Events pe1 on pe0.HomeId = pe1.HomeId and pe1.Sequence = pe0.Sequence - 1 and abs(timediff( pe0.Stamp, pe1.Stamp )) > 10
join Phone_Events pe2 on pe0.HomeId = pe2.HomeId and pe2.Sequence = pe0.Sequence + 1 and pe2.EventId = 22
join Phone_Events pe4 on pe4.HomeId = pe0.HomeId and pe4.EventId = 30 and pe4.Stamp > pe0.Stamp
where pe0.eventId = 12 and pe0.HomeId = 111
AND
    NOT EXISTS(SELECT * FROM Phone_Events pe3
               WHERE pe3.HomeId = pe0.HomeId
               AND pe3.EventId not in( 13, 22 ) 
               AND pe3.Stamp > pe0.Stamp and pe3.Stamp < pe4.Stamp );

是否有某些特定的自我加入会使这种情况变得缓慢?有没有更好的方法来优化它?杀手似乎是“不存在”部分 - 这部分是为了确保最后一个'挂钩'和当前'摘机'之间没有事件。

编辑:EventId如下:

'1', 'device connection'  
'2', 'device disconnection'  
'3', 'device alarm'
'11', 'ring start'
'12', 'off hook'
'13', 'hang up(other end)'
'15', 'missed call'
'21', 'caller id'
'22', 'dtmf'
'24', 'device error'
'30', 'on hook'
'31', 'ring stop'

1 个答案:

答案 0 :(得分:1)

根据新信息完成重写。我接近这个问题的方法是从一个最内部的查询开始,根据HomeID = 111获取我们关心的所有记录,并确保它们按序列ID预先排序(在HomeID,Sequence上有索引)。众所周知,通过拿起电话开始电话 - eventID = 12,拨打音频 - eventid = 22,拨出,有人接听,直到电话重新开机 - eventid = 30) 。如果它是挂断(eventid = 13),我们想忽略它。

我不知道你为什么要查看当前调用的序列#PRIOR,不知道它是否真的有任何影响。看起来你只是试图接通完成的电话以及持续时间。也就是说,我将删除LEFT JOIN Phone_Event的部分和相应的WHERE子句。当你想要解决这个问题时,它可能就在那里。

无论如何,回到逻辑。最内层保证了呼叫顺序的顺序。你不会同时打两个电话。因此,首先将它们按顺序排列,然后我加入SQLVars(为查询创建内联变量@NextCall)。这样做的目的是在每次新呼叫即将开始时识别(EventID = 12)。如果是这样,请记录序列号,然后保存。在下次调用之前,这将保持不变,因此所有其他“事件ID”将具有相同的“起始序列ID”。另外,我正在寻找其他事件......基于起始序列+1的事件= 22并将其设置为标志。然后,基于呼叫开始的最大时间(仅在eventid = 12时设置)和呼叫结束(eventid = 30),最后根据您的挂机检查标记(eventid = 13),即:如果是挂断而没有连接,请不要考虑呼叫。

通过分组,我实质上是将每个呼叫汇总到自己的线路......按归属ID分组,以及用于启动实际电话呼叫的序列号。一旦完成,我可以查询数据并计算呼叫持续时间,因为开始/结束时间在同一行,没有涉及自我自我加入。

最后,where子句......踢出任何一个HANG UP HANG UP的电话。同样,我不知道你是否仍然需要起始呼叫的时间是最后结束事件的元素。

SELECT
      PreGroupedCalls.*,
      timediff( PreGroupedCalls.CallEndTime, PreGroupedCalls.CallStartTime ) CallDuration 
   from
      ( SELECT
              Calls.HomeID,
              @NextCall := @NextCall + if( Calls.EventID = 12, Calls.Sequence, @NextCall ) as NextNewCall,
              MAX( if( Calls.EventID = 12, Calls.Stamp, 0 )) as CallStartTime,
              MAX( if( Calls.EventID = 30, Calls.Stamp, 0 )) as CallEndTime,
              MAX( if( Calls.EventID = 22 and Calls.Sequence = @NewCallFirstSeq +1, 1, 0 )) as HadDTMFEntry,
              MAX( if( Calls.EventID = 13 and Calls.Sequence = @NewCallFirstSeq +1, 1, 0 )) as WasAHangUp
           from 
              ( select pe.HomeId, 
                       pe.Sequence,
                       pe.EventID,
                       pe.Stamp
                   from
                      Phone_Events pe
                   where 
                      pe.HomeID = 111
                   order by
                      pe.Sequence ) Calls,
              ( select @NextCall := 0 ) SQLVars
           group by
              Calls.HomeID,
              NextNewCall ) PreGroupedCalls

         LEFT JOIN Phone_Event PriorCallEvent
            ON PreGroupCalls.NextNewCall = PriorCallEvent.Sequence -1 
   where
         PreGroupedCalls.WasHangUp = 0
      AND ( PriorCallEvent.Sequence IS NULL
         OR abs(timediff( PriorCallEvent.Stamp, PreGroupedCalls.CallStartTime )) > 10 )

来自反馈/错误的评论

要尝试修复DOUBLE错误,您显然需要在SQLVars选择中稍作修改..尝试以下

(选择@NextCall:= CAST(0作为INT))SQLVars

现在,IF()正在做什么...让我们来看看。

@NextCall + if(Calls.EventID = 12,Calls.Sequence,@ NextCall)

表示查看事件ID。如果它是12(即:摘机),则抓取该条目的序列号。这将成为另一个电话的新“起始序列”。如果没有,只需保留上次设置的值,因为它是正在进行的呼叫的延续。现在,让我们看一些模拟数据,以帮助更好地说明所有列

Original data                    Values that will ultimately be built into...
HomeID Sequence EventID Stamp    @NextCall
111    1        12      8:00:00    1  beginning of a new call
111    2        22      8:00:01    1  not a new "12" event, keep last value
111    3        30      8:05:00    1  call ended, phone back on hook
111    4        12      8:09:00    4  new call, use the sequence of THIS entry
111    5        22      8:09:01    4  same call
111    6        13      8:09:15    4  same call, but a hang up
111    7        30      8:09:16    4  same call, phone back on hook
111    8        12      8:15:30    8  new call, get sequence ID
111    9        22      8:15:31    8  same call...
111   10        30      8:37:15    8  same call ending...

Now, the query SHOULD create something like this
HomeID   NextNewCall   CallStartTime  CallEndTime   HadDTMFEntry  WasAHangUp
111      1             8:00:00        8:05:00       1             0
111      4             8:09:00        8:09:16       1             1
111      8             8:15:30        8:37:15       1             0

正如您所看到的,@ NextCall将给定调用的所有顺序条目保持在一起“Grouped”,这样您就不必使用大于span的信息或者小于...的信息总是会跟随某些“事件”路径,所以无论启动呼叫的是其他事件的基础,直到下一个呼叫开始,然后抓取THAT序列进行该组呼叫。

是的,它需要掌握很多......但希望现在更容易消化:)