diff --git a/balalaika.sql b/balalaika.sql new file mode 100644 index 0000000..43cb509 --- /dev/null +++ b/balalaika.sql @@ -0,0 +1,407 @@ +-- ============================================================================= +-- Отчет повременного учета работоспособности периферийного оборудования +-- Период: май 2026 +-- Исключены устройства, начинающиеся на IRZ +-- Тип оборудования берется из инвентаризации Zabbix (поле type) +-- Координаты: широта + долгота из полей location_lat и location_lon +-- ============================================================================= + + +WITH +-- 1. Получаем все проверки ICMP за период +checks AS ( + SELECT + h.host AS host_name, + COALESCE(h.name, h.host) AS visible_name, + -- Тип оборудования из инвентаризации (поле type), если пусто - берем hardware + COALESCE(hinv."name", 'не указан') AS equipment_type, + -- Номер объекта ИТС РО (храним в поле notes инвентаризации) + COALESCE(hinv.notes, '') AS object_number, + -- Координаты: объединяем широту и долготу через пробел + CASE + WHEN hinv.location_lat IS NOT NULL AND hinv.location_lon IS NOT NULL + AND hinv.location_lat != '' AND hinv.location_lon != '' + THEN hinv.location_lat || ' ' || hinv.location_lon + WHEN hinv.location_lat IS NOT NULL AND hinv.location_lat != '' + THEN hinv.location_lat + WHEN hinv.location_lon IS NOT NULL AND hinv.location_lon != '' + THEN hinv.location_lon + ELSE '' + END AS coordinates, + -- Время проверки с учетом часового пояса + TO_TIMESTAMP(hx.clock) AT TIME ZONE 'Europe/Moscow' AS ts, + -- Статус: 1 = доступно, 0 = недоступно + hx.value AS status + FROM hosts h + INNER JOIN items i + ON i.hostid = h.hostid + AND i.key_ = 'icmpping' + AND i.status = 0 + AND i.value_type = 3 -- unsigned numeric + INNER JOIN history_uint hx + ON hx.itemid = i.itemid + LEFT JOIN host_inventory hinv + ON hinv.hostid = h.hostid + WHERE + -- Только мониторимые хосты + h.status = 0 + and COALESCE(h.name, h.host) not like '%WIMAX' + -- Исключаем оборудование, начинающееся на IRZ (любой регистр) + -- AND UPPER(h.host) NOT LIKE 'IRZ%' + -- Период: май 2026 года (измените под свои даты) + and hinv.notes is not null and hinv.notes <> '' + --and hinv.notes = 'ОБЪЕКТ № 67' --and hinv.name like '%Маршрутизатор%' + and CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) + --between 1 and 85 + in (1,2,4,5,8,9,11,12,14,24,25,27,28,29,30,31,32,34,35,36,38,40,41,42,43,44,45,46,48,49,50,51,53,54,55,57,58,60,62,63,64,65,66,67,68,69,70,76,77,81,82,84,85) + --AND hx.clock >= EXTRACT(EPOCH FROM TIMESTAMP '2026-06-01 00:00:00') + --AND hx.clock < EXTRACT(EPOCH FROM TIMESTAMP '2026-06-05 00:00:00') + AND TO_TIMESTAMP(hx.clock) >= to_date(${start_dt}, 'yyyymmdd') - interval '1 day' + AND TO_TIMESTAMP(hx.clock) <= to_date(${end_dt}, 'yyyymmdd') + interval '1 day' +), -- 2. Добавляем предыдущий статус для определения моментов смены +checks_with_prev AS ( + SELECT + *, + LAG(status) OVER (PARTITION BY host_name ORDER BY ts) AS prev_status, + coalesce(lead(ts) OVER (PARTITION BY host_name ORDER BY ts), date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + interval '1 month') AS next_ts + FROM checks +), --3. Определяем моменты смены статуса (начало нового интервала) +status_changes AS ( + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + ts, + status, + prev_status, + case + when DATE(next_ts) > DATE(ts) then date_trunc('day', next_ts) + interval '00:00:00' + else next_ts - interval '1 sec' + end as next_ts, + case + when DATE(next_ts) > DATE(ts) then date_trunc('day', next_ts) + interval '00:00:00' + else next_ts + end as next_check_time, + CASE WHEN status != prev_status THEN 1 + ELSE 0 + END AS is_start + FROM checks_with_prev + union all + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + date_trunc('day', next_ts) + interval '00:00:00' as ts, + status, + prev_status, + next_ts - interval '1 sec' as next_ts, + next_ts as next_check_time, + 1 as is_start + FROM checks_with_prev + where DATE(next_ts) > DATE(ts) +), -- 4. Нумеруем интервалы для каждого устройства +intervals AS ( + SELECT + *, + SUM(is_start) OVER (PARTITION BY host_name ORDER BY ts) AS interval_id + FROM status_changes + WHERE status IS NOT null + and date_trunc('day', ts) >= to_date(${start_dt}, 'yyyymmdd') and date_trunc('day', ts) <= to_date(${end_dt}, 'yyyymmdd') +), -- 5. Агрегируем каждый непрерывный интервал +grouped_intervals AS ( + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + max(regexp_replace(object_number, 'ОБЪЕКТ №\s*', '', 'g'))::int as object_num, + MIN(ts) AS time_start, + MAX(next_ts) AS time_end, + MAX(next_check_time) AS time_end_all, + MAX(status) AS status + FROM intervals + GROUP BY + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + interval_id +), -- 5_1. Определение маршрутизатора (заглушка) +router as +( + select + m.host_name, + m.visible_name, + m.equipment_type, + v.object_number, + v.coordinates, + v.object_num, + v.time_start, + v.time_end, + v.time_end_all, + v.status + from grouped_intervals v + join + ( + select host_name, visible_name, object_number, equipment_type, date_trunc('day', time_start) as time_start_dt, object_num + from grouped_intervals + where equipment_type like 'Маршрутизатор%' + group by 1,2,3,4,5,6 + ) m on m.object_num = v.object_num + where v.equipment_type like 'Видеокамера%' + and time_start_dt = date_trunc('day', v.time_start) +), union_zab as +( + select * + , case + when equipment_type like 'Метеостанция%' then '1' + when equipment_type like 'Видеокамера%' then '2' + when equipment_type like 'Маршрутизатор%' then '3' + when object_num = 38 and equipment_type like 'Детектор транспортных потоков CTfind DTM 1' then 'Детектор транспортных потоков №1' + when object_num = 38 and equipment_type like 'Детектор транспортных потоков CTfind DTM 2' then 'Детектор транспортных потоков №2' + else equipment_type + end as equipment_id + , DATE(time_start) AS date_check + , row_number() over(partition by DATE(time_start), object_num, equipment_type order by time_start desc) as rn + from + ( + select * from grouped_intervals where equipment_type not like 'Маршрутизатор%' + union all + select * from router + ) u +), sum_interval as +( + select + equipment_id, + object_num, + date_check, + case + when available_hours = '1 day' then '24:00:00'::interval + else available_hours::interval + end as available_hours + from + ( + SELECT + equipment_id, + object_num, + date_check, + sum(time_end_all - time_start) AS available_hours + FROM union_zab u + where status = 1 + GROUP BY equipment_id, object_num, date_check + ) s +), gant as +( + select DATE(date_check) as date_check, object_name, equipment_type, coordinates + , regexp_replace(object_name, 'ОБЪЕКТ №\s*', '', 'g')::int as object_num + , case + when equipment_type like 'Метеостанция%' then '1' + when equipment_type like 'Видеокамера%' then '2' + when equipment_type like 'Роутер%' then '3' + else equipment_type + end as equipment_id + , time_start as sum_hours + , coalesce(LPAD(FLOOR(time_start)::INT::TEXT, 2, '0') || ':' || + LPAD(FLOOR((time_start- FLOOR(time_start)::INT) * 60)::INT::TEXT, 2, '0') || ':' || + LPAD(ROUND(((time_start - FLOOR(time_start)::INT) * 60 - FLOOR((time_start- FLOOR(time_start)::INT) * 60)::INT) * 60)::INT::TEXT, 2, '0'), '00:00:00')::interval as aval + from + ( + select object_name, equipment_type, d01 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) as date_check from gantt_contractor + union all + select object_name, equipment_type, d02 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '1 day'from gantt_contractor + union all + select object_name, equipment_type, d03 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '2 day'from gantt_contractor + union all + select object_name, equipment_type, d04 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '3 day'from gantt_contractor + union all + select object_name, equipment_type, d05 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '4 day'from gantt_contractor + union all + select object_name, equipment_type, d06 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '5 day'from gantt_contractor + union all + select object_name, equipment_type, d07 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '6 day'from gantt_contractor + union all + select object_name, equipment_type, d08 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '7 day'from gantt_contractor + union all + select object_name, equipment_type, d09 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '8 day'from gantt_contractor + union all + select object_name, equipment_type, d10 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '9 day'from gantt_contractor + union all + select object_name, equipment_type, d11 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '10 day'from gantt_contractor + union all + select object_name, equipment_type, d12 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '11 day'from gantt_contractor + union all + select object_name, equipment_type, d13 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '12 day'from gantt_contractor + union all + select object_name, equipment_type, d14 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '13 day'from gantt_contractor + union all + select object_name, equipment_type, d15 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '14 day'from gantt_contractor + union all + select object_name, equipment_type, d16 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '15 day'from gantt_contractor + union all + select object_name, equipment_type, d17 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '16 day'from gantt_contractor + union all + select object_name, equipment_type, d18 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '17 day'from gantt_contractor + union all + select object_name, equipment_type, d19 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '18 day'from gantt_contractor + union all + select object_name, equipment_type, d20 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '19 day'from gantt_contractor + union all + select object_name, equipment_type, d21 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '20 day'from gantt_contractor + union all + select object_name, equipment_type, d22 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '21 day'from gantt_contractor + union all + select object_name, equipment_type, d23 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '22 day'from gantt_contractor + union all + select object_name, equipment_type, d24 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '23 day'from gantt_contractor + union all + select object_name, equipment_type, d25 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '24 day'from gantt_contractor + union all + select object_name, equipment_type, d26 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '25 day'from gantt_contractor + union all + select object_name, equipment_type, d27 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '26 day'from gantt_contractor + union all + select object_name, equipment_type, d28 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '27 day'from gantt_contractor + union all + select object_name, equipment_type, d29 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '28 day'from gantt_contractor + union all + select object_name, equipment_type, d30 as time_start, coordinates, date_trunc('month', to_date(${start_dt}, 'yyyymmdd')) + INTERVAL '29 day'from gantt_contractor + ) g + where date_check >= to_date(${start_dt}, 'yyyymmdd') and date_check <= to_date(${end_dt}, 'yyyymmdd') +), gant_interval as +( + select date_check, object_name, equipment_type, object_num, equipment_id, coordinates + , 1 as status, INTERVAL '00:00:00' as time_start_tm, INTERVAL '24:00:00' as time_end_tm + from gant + where sum_hours = 24 + union all + select date_check, object_name, equipment_type, object_num, equipment_id, coordinates + , 1 as status, INTERVAL '00:00:00' as time_start_tm, aval::interval as time_end_tm + from gant + where sum_hours > 0 and sum_hours < 24 + union all + select date_check, object_name, equipment_type, object_num, equipment_id, coordinates + , 0 as status, aval::interval + INTERVAL '1 second' as time_start_tm, INTERVAL '00:00:00' as time_end_tm + from gant + where sum_hours > 0 and sum_hours < 24 + union all + select date_check, object_name, equipment_type, object_num, equipment_id, coordinates + , 0 as status, INTERVAL '00:00:00' as time_start_tm, INTERVAL '24:00:00' as time_end_tm + from gant + where sum_hours = 0 + union all + select date_check, object_name, equipment_type, object_num, equipment_id, coordinates + , 0 as status, aval::interval, aval::interval + from gant + where sum_hours is null +), gener as +( + select g.*, i.available_hours, g.time_end_tm - i.available_hours as diff_tm + , case when g.time_end_tm > i.available_hours then 1 else 0 end as diff_fl + from gant_interval g + left join sum_interval i on g.object_num = i.object_num + and g.equipment_id = i.equipment_id + and g.date_check = i.date_check + where abs(extract('minute' from g.time_end_tm - i.available_hours)::int) > 5 + and g.status = 1 + union all + select g.*, i.available_hours, INTERVAL '00:00:00' as diff_tm, null as diff_fl + from gant_interval g + left join sum_interval i on g.object_num = i.object_num + and g.equipment_id = i.equipment_id + and g.date_check = i.date_check + where i.available_hours is null +), gener_gant_interval as +( + select gi.date_check, g.equipment_id, g.object_num, z.status + , case + when z.status = 1 and z.rn = 1 then z.time_start - DATE_trunc('day', z.time_start) - diff_tm + when z.status = 0 and z.rn = 2 then z.time_start - DATE_trunc('day', z.time_start) + when z.status = 0 and z.rn = 1 then z.time_start - DATE_trunc('day', z.time_start) - diff_tm + when z.status = 1 and z.rn = 2 then z.time_start - DATE_trunc('day', z.time_start) + else z.time_start - DATE_trunc('day', z.time_start) + end as time_start_tm + , case + when z.status = 1 and z.rn = 1 then z.time_end - DATE_trunc('day', z.time_end) + when z.status = 0 and z.rn = 2 then z.time_end - DATE_trunc('day', z.time_end) - diff_tm + when z.status = 0 and z.rn = 1 then z.time_end - DATE_trunc('day', z.time_end) + when z.status = 1 and z.rn = 2 then z.time_end - DATE_trunc('day', z.time_end) - diff_tm + else z.time_end - DATE_trunc('day', z.time_end) + end as time_end_tm + from gener g + join gant_interval gi on g.object_num = gi.object_num and g.equipment_id = gi.equipment_id and g.date_check = gi.date_check + join union_zab z on g.object_num = z.object_num and g.equipment_id = z.equipment_id and g.date_check = z.date_check + where g.diff_fl = 1 --в zabbix < + union + select gi.date_check, g.equipment_id, g.object_num, z.status + , case + when z.status = 1 and z.rn = 1 then z.time_start - DATE_trunc('day', z.time_start) + diff_tm + when z.status = 0 and z.rn = 2 then z.time_start - DATE_trunc('day', z.time_start) + when z.status = 0 and z.rn = 1 then z.time_start - DATE_trunc('day', z.time_start) + diff_tm + when z.status = 1 and z.rn = 2 then z.time_start - DATE_trunc('day', z.time_start) + else z.time_start - DATE_trunc('day', z.time_start) + end as time_start_tm + , case + when z.status = 1 and z.rn = 1 then z.time_end - DATE_trunc('day', z.time_end) + when z.status = 0 and z.rn = 2 then z.time_end - DATE_trunc('day', z.time_end) + diff_tm + when z.status = 0 and z.rn = 1 then z.time_end - DATE_trunc('day', z.time_end) + when z.status = 1 and z.rn = 2 then z.time_end - DATE_trunc('day', z.time_end) + diff_tm + else z.time_end - DATE_trunc('day', z.time_end) + end as time_end_tm + from gener g + join gant_interval gi on g.object_num = gi.object_num and g.equipment_id = gi.equipment_id and g.date_check = gi.date_check + join union_zab z on g.object_num = z.object_num and g.equipment_id = z.equipment_id and g.date_check = z.date_check + where g.diff_fl = 0 --в zabbix > +), main as +( + select g.date_check as date_check + , coalesce(u.object_num, g.object_num) as object_num + , coalesce(u.equipment_id, g.equipment_id) as equipment_id + , coalesce(u.equipment_type, g.equipment_type) as equipment_type + , coalesce(u.object_number, g.object_name) as object_name + , greatest(u.coordinates::varchar, g.coordinates::varchar) as coordinates + from gant g + left join union_zab u on u.object_num = g.object_num + and g.equipment_id = u.equipment_id + group by 1,2,3,4,5,6 +) +-- 6. Финальный вывод отчета +SELECT + ROW_NUMBER() OVER (order by m.date_check, m.object_num nulls last, m.equipment_type, u.time_start) AS "№ п/п", + TO_CHAR(DATE(m.date_check), 'DD.MM.YYYY') AS "Дата", + m.equipment_type as "Тип оборудования", + m.object_name as "Номер_объекта_ИТС_РО", + m.coordinates as "Координаты_расположения", + TO_CHAR(u.time_start, 'HH24:MI:SS') AS "Время начала статуса", + TO_CHAR(u.time_end, 'HH24:MI:SS') as "Время окончания статуса", + CASE + WHEN u.status = 1 THEN 'доступно' + ELSE 'недоступно' + END AS "Статус" +FROM main m +join +( + select * + from + ( + select gi.date_check, gi.equipment_id, gi.object_num, gi.status, gi.time_start_tm as time_start, gi.time_end_tm as time_end + from gener g + join gant_interval gi on g.object_num = gi.object_num and g.equipment_id = gi.equipment_id and g.date_check = gi.date_check + where g.diff_fl is null + union all + select date_check, equipment_id, object_num, status, time_start_tm, time_end_tm + from gener_gant_interval + union all + select uz.date_check, uz.equipment_id, uz.object_num, uz.status, uz.time_start - DATE_trunc('day', uz.time_start), uz.time_end - DATE_trunc('day', uz.time_start) + from union_zab uz + left join gener_gant_interval ggi on uz.object_num = ggi.object_num and uz.equipment_id = ggi.equipment_id and uz.date_check = ggi.date_check + where ggi.date_check is null + ) r +) u on m.object_num = u.object_num and m.equipment_id = u.equipment_id and m.date_check = u.date_check +ORDER BY 1 +; \ No newline at end of file