-- ============================================================================= -- Отчет повременного учета работоспособности периферийного оборудования -- Период: май 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 h.host not like '%WiMAX' and h.host not like '%DP%' and h.host not like '%CO%' --and CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) = 6 and ((CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) in (1,2,4,5,8,9,11,12,14,23,24,25,27,28,29,30,31,32,34,35,36,38,39,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,75,76,77,81,82,84,85) 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' ) or (CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) in (6,10,16,74,78) AND TO_TIMESTAMP(hx.clock) >= to_date('20260709', '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('day', ts) + interval '1 day') 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 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, 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 ((regexp_replace(object_number, 'ОБЪЕКТ №\s*', '', 'g')::INT in (6,10,16,74,78) AND date_trunc('day', ts) >= to_date('20260709', 'yyyymmdd') AND date_trunc('day', ts) <= to_date(${end_dt}, 'yyyymmdd')) or (regexp_replace(object_number, 'ОБЪЕКТ №\s*', '', 'g')::INT not in (6,10,16,74,78) and date_trunc('day', ts) >= to_date(${start_dt}, 'yyyymmdd') and date_trunc('day', ts) <= to_date(${end_dt}, 'yyyymmdd'))) and ts <= now() ), -- 5. Агрегируем каждый непрерывный интервал grouped_intervals AS ( SELECT host_name, visible_name, equipment_type, object_number, coordinates, max(case when object_number similar to '%№%' then regexp_replace(object_number, 'ОБЪЕКТ №\s*', '', 'g')::int else null end) as object_num, MIN(ts) AS time_start, MAX(next_ts) AS time_end, 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.status from grouped_intervals v join ( select host_name, visible_name, object_number, equipment_type, date_trunc('day', time_start) as time_start_dt from grouped_intervals where equipment_type like 'Маршрутизатор%' group by 1,2,3,4,5 ) m on m.object_number = v.object_number where v.equipment_type like 'Видеокамера%' and time_start_dt = date_trunc('day', v.time_start) ) -- 6. Финальный вывод отчета SELECT ROW_NUMBER() OVER (order by object_num nulls last, equipment_type,time_start) AS "№ п/п", TO_CHAR(DATE(time_start), 'DD.MM.YYYY') AS "Дата", equipment_type as "Тип оборудования", case when object_num is null then 'на инвентаризации' else object_number end as "Номер_объекта_ИТС_РО", coordinates as "Координаты_расположения", TO_CHAR(time_start, 'HH24:MI:SS') AS "Время начала статуса", TO_CHAR(time_end, 'HH24:MI:SS') as "Время окончания статуса", CASE WHEN status = 1 THEN 'доступно' ELSE 'недоступно' END AS "Статус" FROM ( select * from grouped_intervals where equipment_type not like 'Маршрутизатор%' union all select * from router ) g ORDER BY 1 ;