From f3eeae39ccf33d4c36313dee4159cb7c45652fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Sun, 28 Jun 2026 20:07:09 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20hardwareAvailabilityReport.sql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Исправления в расчетах Исправления в отображении посуточной статистики --- hardwareAvailabilityReport.sql | 154 ++++++++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 23 deletions(-) diff --git a/hardwareAvailabilityReport.sql b/hardwareAvailabilityReport.sql index 81307d3..d3522ff 100644 --- a/hardwareAvailabilityReport.sql +++ b/hardwareAvailabilityReport.sql @@ -1,10 +1,14 @@ WITH +-- 1. Получаем все проверки ICMP за период checks AS ( SELECT h.host AS host_name, COALESCE(h.name, h.host) AS visible_name, - COALESCE(hinv.name, 'unknown') AS equipment_type, + -- Тип оборудования из инвентаризации (поле 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 != '' @@ -15,44 +19,145 @@ checks AS ( THEN hinv.location_lon ELSE '' END AS coordinates, + -- Время проверки с учетом часового пояса TO_TIMESTAMP(hx.clock) AT TIME ZONE 'Europe/Moscow' AS check_time, + -- Статус: 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 + 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 CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) in (44,77,43,54,27,48,12,49,65,9,60,35,38,1,51,41,53,84,8) + -- Исключаем оборудование, начинающееся на IRZ (любой регистр) --AND UPPER(h.host) NOT LIKE 'IRZ%' - AND hx.clock >= EXTRACT(EPOCH FROM TIMESTAMP '2026-05-25 00:00:00') - AND hx.clock < EXTRACT(EPOCH FROM TIMESTAMP '2026-06-01 00:00:00') + -- Период: май 2026 года (измените под свои даты) + --and hinv.notes = 'ОБЪЕКТ № 12' + --and hinv."name" = 'Видеокамера Trassir TR-D6254IR15' + and CAST(nullif(regexp_replace(hinv.notes, 'ОБЪЕКТ №\s*', '', 'g'),'') as INTEGER) + between 1 and 85 --in (44,77,43,54,27,48,12,49,65,9,60,35,38,1,51,41,53,84,8) + 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' + --and hinv.notes is null + ), -daily_stats AS ( +-- 2. Добавляем предыдущий статус для определения моментов смены +checks_with_prev AS ( + SELECT + *, + LAG(status) OVER (PARTITION BY host_name ORDER BY check_time) AS prev_status, + coalesce(lead(check_time) OVER (PARTITION BY host_name ORDER BY check_time), date_trunc('day', check_time) + interval '1 day - 1 sec') AS next_check_time + FROM checks +), +--select * from ( +-- 3. Определяем моменты смены статуса (начало нового интервала) +status_changes AS ( + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + check_time, + status, + prev_status, + case + when DATE(next_check_time) > DATE(check_time) then date_trunc('day', check_time) + interval '23:59:59' + else next_check_time - interval '1 sec' + end as next_check_time, + CASE WHEN status != prev_status THEN 1 + ELSE 0 + END AS is_start, + case + when DATE(next_check_time) > DATE(check_time) then 1 + else 0 + end as is_24hh + FROM checks_with_prev + union all + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + date_trunc('day', next_check_time) + interval '00:00:00' as check_time, + status, + prev_status, + next_check_time - interval '1 sec', + 1 as is_start, + 0 as is_24hh + FROM checks_with_prev + where DATE(next_check_time) > DATE(check_time) --)f where status != prev_status and date_trunc('sec', ts) = date_trunc('sec', next_check_time) +), + +-- 4. Нумеруем интервалы для каждого устройства +intervals AS ( + SELECT + *, + SUM(is_start) OVER (PARTITION BY host_name ORDER BY check_time) AS interval_id + FROM status_changes + WHERE status IS NOT null + and date_trunc('day', check_time) >= to_date(${start_dt}, 'yyyymmdd') and date_trunc('day', check_time) <= to_date(${end_dt}, 'yyyymmdd') +), + +-- 5. Агрегируем каждый непрерывный интервал +grouped_intervals AS ( SELECT host_name, visible_name, equipment_type, object_number, coordinates, - DATE(check_time) AS check_date, - ROUND((SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) * 60.0) / 3600.0, 2) AS available_hours, - ROUND((SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) * 60.0) / 3600.0, 2) AS unavailable_hours - FROM checks + MIN(date_trunc('second', check_time)) AS check_time, + MAX(date_trunc('second', next_check_time)) AS next_check_time, + MAX(status) AS status, + MAX(is_24hh) AS is_24hh + FROM intervals GROUP BY - host_name, + host_name, visible_name, equipment_type, object_number, coordinates, - DATE(check_time) + interval_id +), + +daily_stats AS ( + select *, + extract('hour' from available_hours)::int AS a_h, + extract('minute' from available_hours)::int AS a_m, + extract('second' from available_hours)::int AS a_s, + extract('hour' from unavailable_hours)::int AS u_h, + extract('minute' from unavailable_hours)::int AS u_m, + extract('second' from unavailable_hours)::int AS u_s + from + ( + SELECT + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + DATE(check_time) AS check_date, + sum(case when status = 1 then next_check_time - check_time else null end) AS available_hours, + sum(case when status = 0 then next_check_time - check_time else null end) AS unavailable_hours + FROM grouped_intervals + GROUP BY + host_name, + visible_name, + equipment_type, + object_number, + coordinates, + DATE(check_time) + ) f ), daily_graph AS ( @@ -61,8 +166,11 @@ daily_graph AS ( 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, STRING_AGG( - TO_CHAR(check_date, 'DD.MM') || ': Доступно ' || available_hours || 'h, Недоступно ' || unavailable_hours || 'h', + TO_CHAR(check_date, 'DD.MM') + || ': Доступно ' || a_h || 'ч.' || a_m || 'м.' || a_s|| 'с.' + || ', Недоступно ' || u_h || 'ч.' || u_m || 'м.' || u_s ||'с.', '; ' ORDER BY check_date ) AS daily_schedule FROM daily_stats @@ -79,8 +187,8 @@ device_summary AS ( equipment_type, object_number, coordinates, - SUM(CASE WHEN available_hours > 12 THEN 1 ELSE 0 END) AS days_available_gt_12h, - SUM(CASE WHEN available_hours <= 12 THEN 1 ELSE 0 END) AS days_unavailable_ge_12h + SUM(CASE WHEN (a_h + a_m/60 + a_s/360) > 12 THEN 1 ELSE 0 END) AS days_available_gt_12h, + SUM(CASE WHEN (a_h + a_m/60 + a_s/360) <= 12 THEN 1 ELSE 0 END) AS days_unavailable_ge_12h FROM daily_stats GROUP BY visible_name, @@ -90,14 +198,14 @@ device_summary AS ( ) SELECT - ROW_NUMBER() OVER (ORDER BY dg.equipment_type) AS row_number, - dg.equipment_type AS equipment_type, - dg.object_number AS object_number, - dg.coordinates AS coordinates, - dg.daily_schedule AS daily_schedule, - ds.days_available_gt_12h AS days_available_more_than_12h, - ds.days_unavailable_ge_12h AS days_unavailable_12h_or_more + ROW_NUMBER() OVER (ORDER BY dg.object_num nulls last, dg.equipment_type) AS "№ п/п", + dg.equipment_type AS "Тип оборудования", + case when dg.object_num is null then 'на инвентаризации' else dg.object_number end AS "Номер_объекта_ИТС_РО", + dg.coordinates AS "Координаты_расположения", + dg.daily_schedule AS "Посуточный график функц оборуд", + ds.days_available_gt_12h AS "Количество суток доступности", + ds.days_unavailable_ge_12h AS "Количество суток недоступности" FROM daily_graph dg INNER JOIN device_summary ds ON ds.visible_name = dg.visible_name -ORDER BY dg.object_number; \ No newline at end of file +ORDER BY 1; \ No newline at end of file