Oracle SQL - 两个日期之间的秒数,忽略维护窗口

时间:2014-11-17 16:34:12

标签: sql oracle date

在我的oracle DB中,我有一个包含两列的表'action',start_time和end_time(都是date)。我可以在几秒钟内查询每个操作的操作持续时间,如下所示:

select (end_time-start_time)*24*60*60 as actionDuration from action

我们有一个2小时的维护窗口,00:00 - 02:00。我想忽略在此窗口中发生的操作所经过的时间。

  • 行动可能会开始&在maint窗口外面结束。
  • 行动可能开始&在窗口内结束(忽略这些)。
  • 动作可以在maint窗口内开始并在maint窗口之外结束。只需要在maint窗口外面的秒数计算。
  • 操作可以从外部开始,但在主窗口内终止。只需要在maint窗口外面的秒数计算。

最后一个复杂的案例:行动持续时间可能跨越多个maint窗口

4 个答案:

答案 0 :(得分:1)

如果我对评论的猜测是正确的,那么:

select a.id, max((end_time - start_time) * 1440) - sum(nvl((mend2 - mbeg2), 0) * 1440) duration
  from (select id, start_time, end_time, mbeg, mend,
               case when start_time between mbeg and mend then start_time else maint.mbeg end mbeg2, 
               case when end_time   between mbeg and mend then end_time   else maint.mend end mend2
          from action a left join 
              (select to_date(:PSTART, 'yyyy-mm-dd hh24:mi:ss') + rownum - 1 mbeg, 
                      to_date(:PSTART, 'yyyy-mm-dd hh24:mi:ss') + 2/24 + rownum - 1 mend
                 from dual connect by rownum < :PDAYS) maint
           on (maint.mbeg between start_time and end_time) or (maint.mend between start_time and end_time)
        -- this condition I forgot earlier
        where not (start_time between mbeg and mend 
                   and 
                   end_time   between mbeg and mend)
) a
 group by a.id
 order by a.id;

在这里你需要使用参数:

  • :PSTART - 首次维护的日期和时间
  • :PDAYS - 您想要计算行动持续时间的天数;

现在查询以分钟为单位计算持续时间,如果您需要其他测量单位,请使用另一个数字而不是1440。

UPD 工作原理。

  • 子查询maint使用分层子句connect by根据需要创建任意数量的行(等于从第一个操作到最​​后一个操作的天数)

  • 然后我使用行动表进行左联接。加入条件 - 维护在操作中开始或停止。加入的结果 - 操作列表,其中每个操作都遵循其所有维护,以及操作是否与维护相交 - NULL

  • 然后,如果在行动发生时发生维护的开始或结束。如果在维护期间开始操作,那么我将操作开始作为维护的开始(字段mbeg2

  • 与维护结束时相同的事情(字段mend2);结果 - 字段mbeg2和mend2包含同时发生操作和维护窗口的时间间隔(只是句点的交集)

  • 然后我使用max聚合函数计算操作长度。如果动作非常长且与许多窗口相交,则子查询中会有很多行,这就是我使用max的原因(您也可以使用minavg并获得相同的结果)

  • 然后我计算所有减少的维护间隔的总和(交叉的结果)并从动作长度中减去这个总和

我希望现在很清楚。

答案 1 :(得分:0)

如果日期之间有很多时间,并且如果您正在运行多个记录的报告,这种方法可能会真正扼杀性能,但可能值得一看。它需要操作的主键。我会考虑在表中添加一列来存储已用时间,并在更新触发器中计算它。

当然替换主键列的正确名称:

with action as
(select id, starttime, endtime from action where id = :P_ACTION_ID)
select count(*) seconds from action
where to_number(to_char(starttime + ((level-1) / (24*60*60)) , 'HH24')) between 2 and 23 
CONNECT BY LEVEL <= (endtime-starttime)*24*60*60; 

答案 2 :(得分:0)

select (- case when start_time > to_date(to_char(start_time, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
               then start_time - to_date(to_char(start_time, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
               else 0 end
        + case when end_time > to_date(to_char(end_time, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
               then end_time - to_date(to_char(end_time, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
               else 0 end      
        + (select sum(case when trunc(start_time) + level - 1/24/60/60 > 
                                to_date(to_char(trunc(start_time) + level - 1/24/60/60, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
                           then trunc(start_time) + level - 1/24/60/60 
                                - to_date(to_char(trunc(start_time) + level - 1/24/60/60, 'YYYYMMDD') || '0200', 'YYYYMMDDHH24MI')
                           else 0 end) 
           from dual connect by level <= trunc(end_time) - trunc(start_time)
       )) * 24 * 60 *60 as actionduration 
from action;

我们的想法是以较小的间隔分割你的间隔:
第一天:[00:00,start_time] (带减号的第一种情况)
最后一天:[00:00,end_time] (第二个案例)
之间的日子:[00:00 - 23:59] (标量子查询)

答案 3 :(得分:0)

我知道我在这个问题上错过了这条船,但是我使用与之前的答案截然不同的方法将另一个答案放在一起,并且可以发布它。它不那么优雅(说实话就是彻头彻尾的丑陋),但它似乎有效并且速度非常快。操作开始和结束时间以产生三个单独查询的并集 - 一个用于整天,一个用于第一天,一个用于最后一天。整天可以乘以22小时(24减去维护窗口)来获得秒数。如果需要计算第一天和最后几天,开始和结束时间将向上舍入到02:00。

计算10年的时间需要1/10秒。没有使用CONNECT BY LEVEL来生成行,因此这段时间并不重要。

我在测试时发布,使用虚拟CTE的记录:

with action as
(select 1 id
       ,to_date('16-nov-2014 01:33:32', 'DD-MON-YYYY HH24:MI:SS') starttime
       ,to_date('20-nov-2014 14:05:06', 'DD-MON-YYYY HH24:MI:SS') endtime 
 from dual
 union 
 select 2 id
       ,sysdate starttime
       ,sysdate+365 endtime 
 from dual)
select id, sum(difference) difference from
(select id
      ,(case when endtime between trunc(endtime) 
             and trunc(endtime) + (2/24) 
                then trunc(endtime) 
             else endtime 
       end
        -
       case when endtime between trunc(endtime) 
             and trunc(endtime) + (2/24) 
                then trunc(endtime) 
             else trunc(endtime) + (2/24) 
       end) * 24*60*60 difference
from action
union
select id
      ,(trunc(endtime)-trunc(starttime+1))*22*60*60 difference 
from action
union
select id
      ,(trunc(starttime+1) - case when starttime between trunc(starttime) 
                                   and trunc(starttime) + (2/24) 
                                      then trunc(starttime) + (2/24) 
                                  else starttime 
                             end)*24*60*60 difference
from action)
group by id;

这是一个较长的版本,显示了三个查询的工作原理:

with action as
(select 1 id
       ,to_date('16-nov-2014 01:33:32', 'DD-MON-YYYY HH24:MI:SS') starttime
       ,to_date('20-nov-2014 14:05:06', 'DD-MON-YYYY HH24:MI:SS') endtime 
 from dual
 union 
 select 2 id
       ,sysdate starttime
       ,sysdate+3650 endtime 
 from dual)
select id
      ,'Last day part' description
      ,to_char(case when endtime between trunc(endtime) 
                and trunc(endtime) + (2/24) 
                 then trunc(endtime) 
                 else trunc(endtime) + (2/24) 
               end, 'DD-MON-YYYY HH24:MI:SS') starttime
      ,to_char(case when endtime between trunc(endtime) 
                and trunc(endtime) + (2/24) 
                 then trunc(endtime) 
                 else endtime 
               end, 'DD-MON-YYYY HH24:MI:SS') endtime
      ,(case when endtime between trunc(endtime) 
             and trunc(endtime) + (2/24) 
                then trunc(endtime) 
             else endtime 
       end
        -
       case when endtime between trunc(endtime) 
             and trunc(endtime) + (2/24) 
                then trunc(endtime) 
             else trunc(endtime) + (2/24) 
       end) * 24*60*60 difference
from action
union
select id
      ,'Whole days' description
      ,to_char(trunc(starttime+1), 'DD-MON-YYYY HH24:MI:SS') starttime
      ,to_char(trunc(endtime), 'DD-MON-YYYY HH24:MI:SS') endtime
      ,(trunc(endtime)-trunc(starttime+1))*22*60*60 difference 
from action
union
select id
      ,'First day part' description
      ,to_char(case when starttime between trunc(starttime) 
                     and trunc(starttime) + (2/24) 
                     then trunc(starttime) + (2/24) 
                     else starttime 
                    end, 'DD-MON-YYYY HH24:MI:SS') starttime
      ,to_char(trunc(starttime+1), 'DD-MON-YYYY HH24:MI:SS') endtime
      ,(trunc(starttime+1) - case when starttime between trunc(starttime) 
                                   and trunc(starttime) + (2/24) 
                                      then trunc(starttime) + (2/24) 
                                  else starttime 
                             end)*24*60*60 difference
from action;