Задача: записать в таблицу event_day общее время недоступности системы в формате: дата, время недоступности системы (в секундах).
Два элемента подключены параллельно и два последовательно. Система считается недоступной если есть аварии по элементу 1001 или 1002 или (1003 и 1004).
При отказе отдельного элемента в таблицу event записывается порядковый номер события, номер элемента по которому произошел отказ, время отказа. После починки строка обновляется и в поле end_date записывается дата/время возобновления работы.
Система не может быть "дважды недоступной" в один момент времени. Т.е. если в состоянии аварии больше чем один элемент, авария одна.
По одному элементу в один момент времени в таблице event_day может быть зафиксировано несколько аварий. Например, пользователь_1 видит что система не работает и создает аварию, в тоже время пользователь_2 создает свою аварию и т.д. Авария элемента считается с времени открытия самого первого по время закрытия последнего.
Система поддерживается 5 дней в неделю и только в "бизнес время" (для примера с 8-00 до 18-00). Т.е. если авария случается вне этого диапазона она не должна учитываться.
Таблица источник event:
id | element_id | start_date | end_date |
1 | 1001 | 01.09.2011 5:00:00 | 01.09.2011 10:00:00 |
2 | 1001 | 01.09.2011 9:00:00 | 01.09.2011 11:00:00 |
3 | 1003 | 01.09.2011 12:00:00 | 01.09.2011 14:30:00 |
4 | 1003 | 01.09.2011 12:30:00 | 01.09.2011 16:00:00 |
5 | 1004 | 01.09.2011 13:00:00 | 01.09.2011 15:30:00 |
6 | 1002 | 01.09.2011 13:15:00 | 01.09.2011 13:30:00 |
7 | 1004 | 01.09.2011 16:30:00 | 01.09.2011 17:00:00 |
8 | 1002 | 01.09.2011 17:30:00 | 01.09.2011 19:00:00 |
9 | 1001 | 01.09.2011 17:30:00 |
Авария с id = 9 на момент расчета считается незакрытой.
Данные из таблицы event в виде схемы:
Нужно посчитать в секундах интервалы Т1 - Т4; Т7 - Т11 и Т15 - Т17.
Аварию элемента 1001 до наступления бизнес времени (точка Т1) учитывать не надо. В интервале Т2 - Т3 одновременно открыты 2 аварии, но считать нужно что система не доступна по одной иначе время задвоится.
В момент времени Т5 зафиксирована авария элемента 1003, но т.к. элементы 1003 и 1004 включены параллельно система остается в рабочем состоянии.
В Т6 авария по 1003 зафиксирована повторно. Система остается в рабочем состоянии.
В Т7 авария элемента 1004. Т.к. ранее открыта авария по 1003. С этого момента система считается недоступной.
В Т8 еще авария по элемента 1002. Которую считать не нужно т.к. система и так не доступна и ее учет задвоит время.
В Т9 авария элемента Т2 закрыта, но система остается недоступной из-за аварий элементов 1003 и 1004.
В Т10 закрывается одна из аварий элемента 1003. Например, администратор увидел что одна их несколько и закрыл дублирующую.
В Т11 закрывается авария по элементу 1004 система становится доступной. Хотя авария по 1003 еще не закрыта.
В Т12 закрывается авария по элементу 1003.
Т13-Т14 авария элемента 1004. Но т.к.элемент 1003 работает. Система доступна.
Т15-Т17 аварии по элементам 1001 и 1002. Нужно считать только одну что бы не задвоить время недоступности.
В точке Т17 заканчивается бизнес время. Дальше недоступность системы не учитывается.
На момент расчета, одна из аварий остается незакрытой (поле end_date таблицы event остается пустым).
DECLARE v_date date := to_date('01.09.2011', 'dd.mm.yyyy'); -- день за который производится расчет v_start number := 8; -- начало бизнес времени (с часа) v_end number := 18; -- окончание бизнес времени (до часа) v_day number := 5; -- сколько дней недели считать v_1001 number :=0; v_1002 number :=0; v_1003 number :=0; v_1004 number :=0; v_alarm varchar(1); cursor r_1 is select id ,element_id ,type ,dt start_date ,nvl(lead(DT) over(order by DT ), trunc(v_date) + 18/24) finish_date from ( select id, element_id, 'start_date' type, case when START_DATE < trunc(v_date) + v_start/24 then trunc(v_date) + v_start/24 when START_DATE > trunc(v_date) + v_end/24 then trunc(v_date) + v_end/24 else START_DATE end dt from event where START_DATE < trunc(v_date) + v_end/24 and nvl(FINISH_DATE, sysdate) >= trunc(v_date) + v_start/24 union all select id, element_id, 'finish_date' type, case when nvl(FINISH_DATE, sysdate) < trunc(v_date) + v_start/24 then trunc(v_date) + v_start/24 when nvl(FINISH_DATE, sysdate) > trunc(v_date) + v_end/24 then trunc(v_date) + v_end/24 else FINISH_DATE end dt from event where START_DATE < trunc(v_date) + v_end/24 and nvl(FINISH_DATE, sysdate) >= trunc(v_date) + v_start/24 ); --Объявлены переменные. В курсоре выбраны все аварии которые были в этот день. --Если время начала/окончания вне бизнес времени - приведены к нему. --Все даты сгруппированы в один столбец с указанием номера аварии, элемента, признака что это дата начала или окончания аварии. --При помощи функции LEAD весь день разбит на интервалы. --Начало каждого интервала это открытие или закрытие аварий выстроенные в хронологическом порядке. --Окончание интервала - время следующего события или окончание бизнес времени (если событие последнее в рассматриваемом дне). BEGIN IF to_char(v_date, 'D') <= v_day -- проверка что день с по пятницу THEN FOR t IN r_1 LOOP if t.type = 'start_date' and t.element_id = 1001 then v_1001 := v_1001 +1; elsif t.type = 'finish_date' and t.element_id = 1001 then v_1001 := v_1001 -1; elsif t.type = 'start_date' and t.element_id = 1002 then v_1002 := v_1002 +1; elsif t.type = 'finish_date' and t.element_id = 1002 then v_1002 := v_1002 -1; elsif t.type = 'start_date' and t.element_id = 1003 then v_1002 := v_1003 +1; elsif t.type = 'finish_date' and t.element_id = 1003 then v_1002 := v_1003 -1; elsif t.type = 'start_date' and t.element_id = 1004 then v_1004 := v_1004 +1; elsif t.type = 'finish_date' and t.element_id = 1004 then v_1004 := v_1004 -1; end if; if v_1003 * v_1004 > 0 then v_alarm := 'Y'; elsif v_1001 > 0 then v_alarm := 'Y'; elsif v_1002 > 0 then v_alarm := 'Y'; else v_alarm := 'N'; end if; -- используется временная таблица с которой данные будут вставлятся в итоговую insert into tmp_event te (TE.ID, TE.CI_ID, TE.TYPE, TE.START_DATE, TE.FINISH_DATE, TE.ALARM, TE.SEC) values (t.id, t.element_id, t.type, t.start_date, t.finish_date, v_alarm, (t.finish_date - t.start_date) * 86400 ); commit; END LOOP; END IF; insert into event_day ed (ed.dt, ed.sec) select v_date, nvl(sum(te.sec),0) from tmp_event te where te.alarm = 'Y'; delete tmp_event; commit; END;Проверка что рассматриваемый день не выходной.
Смотрим каждый интервал. Если в нем событие начинается соответствующую переменную увеличиваем на 1, если закрывается - уменьшаем. Таким образом определяем какие аварии в каком интервале значимы.
Для каждого интервала определяем была ли в нем авария. Если произведение переменных v_1003 и v_1004 больше нуля, то по обеим элементам были аварии, а если = 0 - проверяем переменные v_1001 и v_1002. соответствующие признаки записываем во временную таблицу. Также записываем длительность интервалов (в секундах)
Находим суммарную продолжительность всех интервалов где были аварии из временной таблицы в итоговую.
Удаляем все данные из временной таблицы.
Общее время недоступности составило 21600 секунд. Или 6 часов.
Интервал Т1-Т4 с 8-00 по 11-00 это 3 часа;
Интервал Т7-Т11 с 13-00 по 15-30 это 2,5 часа;
Интервал Т15-Т17 с 17-30 по 18-00 это 0,5 часа;
Итого 6 часов.