У меня есть таблица, в которой я храню информацию о сбоях в работе некоторых устройств. Помимо прочей информации он содержит beginTime и endTime, которые обозначают начало и конец отключения соответственно.

+----+---------------------+---------------------+-----+
| 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 |                     |     |
+----+---------------------+---------------------+-----+

Я хочу подготовить отчет, в котором будет показана сумма продолжительности отключений за каждый день. Поскольку в таблице миллионы записей, я не хочу вычислять его каждый раз, когда приложение загружает отчет, и вместо этого я хотел бы сохранить его в базе данных. Я узнал о материализованных представлениях и подумал, что это будет хороший инструмент для этой задачи. Я мог бы обновлять его ежедневно в конце дня. Однако мне сложно написать правильный 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, но я застрял здесь, не зная, куда идти.

Так возможно ли это вообще? Или мне следует использовать стандартную таблицу и немного PL / SQL для ее заполнения? На данный момент я еще не знаю, какие у меня варианты с учетом PL / SQL (например, запускать его ежедневно).

1
Egan Wolf 30 Янв 2019 в 17:08

2 ответа

Лучший ответ

Благодаря ответу @Ponder Stibbons мне удалось найти правильный выбор:

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

демо

Я выполняю правильное соединение со списком всех дат от самой ранней даты в моей таблице до текущей даты и суммирую соответствующие записи.

1
Egan Wolf 31 Янв 2019 в 13:04

Моя попытка:

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

Я сделал самосоединение с разными датами, потом сделал суммирование, подобное вашему. Нулевые значения в 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', если хотите проверить).


Изменить:

... в этом случае мне нужна запись на 17-01 с 24 часами, еще одна на 18-01 с 24 часами и так далее.

Итак, вам нужен генератор дат:

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

Результат:

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
1
Ponder Stibbons 31 Янв 2019 в 13:04