汇总每天的停电持续时间

Aggregate outages' duration time for every day

我有一个 table 存储有关某些设备中断的信息。在其他信息中,它包含 beginTimeendTime,分别表示中断的开始和结束。

+----+---------------------+---------------------+-----+
| Id |      beginTime      |       endTime       | ... |
+----+---------------------+---------------------+-----+
| 10 | 13/01/2019 11:00:00 | 13/01/2019 15:00:00 |     |
| 20 | 13/01/2019 20:00:00 | 14/01/2019 09:00:00 |     |
| 30 | 13/01/2019 18:00:00 | 15/01/2019 10:00:00 |     |
| 40 | 16/01/2019 22:00:00 |                     |     |
+----+---------------------+---------------------+-----+

我想准备报告以显示每天的中断持续时间总和。因为 table 中有数百万条记录,所以我不想在每次应用程序加载报告时都计算它,而是想将它存储在数据库中。我了解了物化视图,并认为它会是完成这项任务的好工具。我可以让它每天在一天结束时刷新。但是,我正在努力编写一个合适的 SQL。假设今天是17-01-2019,想要查看的内容是这样的:

+------------+------+
|    date    | time |
+------------+------+
| 13/01/2019 |   14 |4 hours from 1st + 4 hours from 2nd + 6 hours from 3rd
| 14/01/2019 |   33 |9 hours from 2nd + 24 hours from 3rd
| 15/01/2019 |   10 |10 hours from 3rd
| 16/01/2019 |    2 |2 hours from 4th
+------------+------+

到目前为止我最好的尝试是

select to_char(nvl(endTime, current_timestamp),'YYYY-MM-DD') as date,
sum(time_diff(beginTime, nvl(endTime, current_timestamp))) as time
from ttest
group by to_char(nvl(endTime, current_timestamp),'YYYY-MM-DD');

其中 time_diff 正在计算时间戳之间的差异。这显然是错误的,因为它基于 endTime,但我现在被困在这里,不知道该去哪里。

那么,这可能吗?或者我应该使用标准 table 和一些 PL/SQL 来填充它?在这一点上,我还不知道考虑 PL/SQL 我有什么选择(比如每天触发它)。

我的尝试:

select dt, 24 * sum(nvl2(endtime, least(dt + 1, endtime, dt + 1), dt) 
                  - nvl2(endtime, greatest(begintime, dt), begintime)) duration
  from ttest t
  join (select trunc(nvl(endtime, sysdate)) dt from ttest) d 
    on begintime < dt + 1 and (dt < endtime or endtime is null)
  group by dt order by dt  

dbfiddle demo

我用不同的日期进行了自我连接,然后我做了类似于你的总结。 endtime 中的空值由 nvl2 处理,但您可以将其更改为 case when。结果:

DT            DURATION
----------- ----------
2019-01-13          14
2019-01-14          33
2019-01-15          10
2019-01-30         314

如愿,除了最后一行,因为计算是基于sysdate,所以现在是314小时(但你可以将sysdate更改为任何日期,例如date '2019-01-17'如果你想测试).


编辑:

...in this case I need a record for 17-01 with 24 hours, another one for 18-01 with 24 hours and so on.

所以你需要日期生成器:

select dt + level - 1 dt 
  from (select trunc(min(endtime)) dt from ttest) 
  connect by dt + level - 1 < sysdate)

将其与之前的查询(稍作修改)合并:

with 
  dates as (
    select dt + level - 1 dt 
      from (select trunc(min(endtime)) dt from ttest) 
      connect by dt + level - 1 < sysdate),
  details as (
    select dt, id, begintime, endtime,
           case when endtime is null then dt + 1 else least(dt + 1, endtime) end t2,
           greatest(begintime, dt) t1
      from ttest t join dates on begintime < dt + 1 and (dt < endtime or endtime is null))
select dt, 24 * sum(t2 - t1) duration
  from details group by dt order by dt

dbfiddle demo

结果:

DT            DURATION
----------- ----------
2019-01-13          14
2019-01-14          33
2019-01-15          10
2019-01-16           2
2019-01-17          24
2019-01-18          24
...                ...
2019-01-30          24
2019-01-31          24
19 rows selected

感谢@Ponder Stibbons 的回答,我设法找到了合适的 select:

select dt, nvl(24 * sum(nvl2(endtime, least(dt + 1, endtime), dt+1) 
                  - greatest(begintime, dt)),0) duration
  from ttest t
  right join (select trunc((select min(beginTime) from ttest)) + rownum -1 dt
    from all_objects
      where rownum <= sysdate-cast((select min(beginTime) from ttest) as date)) d 
    on begintime < dt + 1 and (dt < endtime or endtime is null)
  group by dt
  order by dt

demo

我对从我的 table 中的最早日期到当前日期和匹配记录求和的所有日期列表进行右连接。