mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-08-26 07:59:38 +00:00
Compare commits
3 Commits
e468818a26
...
DATA-occup
Author | SHA1 | Date | |
---|---|---|---|
6282f402db | |||
76f3d6369a | |||
14b7f4ab6b |
@ -13,81 +13,142 @@ WITH presence_logs AS (
|
|||||||
AND p.cat_name = 'hps'
|
AND p.cat_name = 'hps'
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
|
raw_absence_intervals AS (
|
||||||
presence_intervals AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
|
device_id,
|
||||||
prev_time AS start_time,
|
prev_time AS start_time,
|
||||||
event_time AS end_time
|
event_time AS end_time
|
||||||
FROM presence_logs
|
FROM presence_logs
|
||||||
WHERE value = 'none'
|
WHERE prev_value = 'none' AND prev_time IS NOT NULL
|
||||||
AND prev_value = 'presence'
|
|
||||||
AND prev_time IS NOT NULL
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Split intervals across days
|
absence_intervals AS (
|
||||||
split_intervals AS (
|
SELECT
|
||||||
|
r.space_id,
|
||||||
|
r.device_id,
|
||||||
|
gs.day,
|
||||||
|
GREATEST(r.start_time, gs.day) AS start_time,
|
||||||
|
LEAST(r.end_time, gs.day + INTERVAL '1 day') AS end_time
|
||||||
|
FROM raw_absence_intervals r
|
||||||
|
CROSS JOIN LATERAL (
|
||||||
|
SELECT generate_series(
|
||||||
|
date_trunc('day', r.start_time),
|
||||||
|
date_trunc('day', r.end_time),
|
||||||
|
INTERVAL '1 day'
|
||||||
|
) AS day
|
||||||
|
) gs
|
||||||
|
WHERE GREATEST(r.start_time, gs.day) < LEAST(r.end_time, gs.day + INTERVAL '1 day')
|
||||||
|
),
|
||||||
|
|
||||||
|
device_counts AS (
|
||||||
|
SELECT space_id, day, COUNT(DISTINCT device_id) AS device_count
|
||||||
|
FROM absence_intervals
|
||||||
|
GROUP BY 1, 2
|
||||||
|
),
|
||||||
|
|
||||||
|
timeline AS (
|
||||||
|
SELECT
|
||||||
|
a.space_id,
|
||||||
|
a.day,
|
||||||
|
a.device_id,
|
||||||
|
a.start_time AS ts,
|
||||||
|
1 AS is_start
|
||||||
|
FROM absence_intervals a
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
a.space_id,
|
||||||
|
a.day,
|
||||||
|
a.device_id,
|
||||||
|
a.end_time AS ts,
|
||||||
|
0 AS is_start
|
||||||
|
FROM absence_intervals a
|
||||||
|
),
|
||||||
|
|
||||||
|
ordered_events AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
generate_series(
|
day,
|
||||||
date_trunc('day', start_time),
|
ts,
|
||||||
date_trunc('day', end_time),
|
is_start,
|
||||||
interval '1 day'
|
device_id,
|
||||||
)::date AS event_date,
|
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
|
||||||
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
OVER (PARTITION BY space_id, day, device_id ORDER BY ts) AS device_active
|
||||||
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
|
FROM timeline
|
||||||
FROM presence_intervals
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Mark and group overlapping intervals per space per day
|
device_state_changes AS (
|
||||||
ordered_intervals AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
event_date,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
|
||||||
FROM split_intervals
|
|
||||||
),
|
|
||||||
|
|
||||||
grouped_intervals AS (
|
|
||||||
SELECT *,
|
|
||||||
SUM(CASE
|
|
||||||
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
|
||||||
FROM ordered_intervals
|
|
||||||
),
|
|
||||||
|
|
||||||
-- Merge overlapping intervals per group
|
|
||||||
merged_intervals AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
event_date,
|
|
||||||
MIN(interval_start) AS merged_start,
|
|
||||||
MAX(interval_end) AS merged_end
|
|
||||||
FROM grouped_intervals
|
|
||||||
GROUP BY space_id, event_date, grp
|
|
||||||
),
|
|
||||||
|
|
||||||
-- Sum durations of merged intervals
|
|
||||||
summed_intervals AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
event_date,
|
day,
|
||||||
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
ts,
|
||||||
FROM merged_intervals
|
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
|
||||||
GROUP BY space_id, event_date
|
OVER (PARTITION BY space_id, day ORDER BY ts) AS net_active_devices
|
||||||
),
|
FROM (
|
||||||
|
SELECT DISTINCT space_id, day, ts, is_start
|
||||||
|
FROM timeline
|
||||||
|
) t
|
||||||
|
),
|
||||||
|
|
||||||
final_data AS (
|
absence_windows AS (
|
||||||
|
SELECT
|
||||||
|
dc.space_id,
|
||||||
|
dc.day,
|
||||||
|
ts AS start_time,
|
||||||
|
LEAD(ts) OVER (PARTITION BY dc.space_id, dc.day ORDER BY ts) AS end_time
|
||||||
|
FROM device_state_changes dsc
|
||||||
|
JOIN device_counts dc ON dc.space_id = dsc.space_id AND dc.day = dsc.day
|
||||||
|
WHERE net_active_devices = 0
|
||||||
|
),
|
||||||
|
|
||||||
|
empty_periods AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
EXTRACT(EPOCH FROM (end_time - start_time)) AS unoccupied_seconds
|
||||||
|
FROM absence_windows
|
||||||
|
WHERE end_time IS NOT NULL
|
||||||
|
),
|
||||||
|
|
||||||
|
unoccupied_summary AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
SUM(unoccupied_seconds) AS total_unoccupied_seconds
|
||||||
|
FROM empty_periods
|
||||||
|
GROUP BY space_id, day
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Include device count even for days with 0 unoccupied time
|
||||||
|
all_days_with_devices AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
DATE(event_time) AS day,
|
||||||
|
COUNT(DISTINCT device_id) AS device_count
|
||||||
|
FROM presence_logs
|
||||||
|
GROUP BY 1, 2
|
||||||
|
),
|
||||||
|
|
||||||
|
final_occupancy AS (
|
||||||
|
SELECT
|
||||||
|
d.space_id,
|
||||||
|
d.day,
|
||||||
|
d.device_count,
|
||||||
|
COALESCE(u.total_unoccupied_seconds, 0) AS unoccupied_seconds,
|
||||||
|
86400 - COALESCE(u.total_unoccupied_seconds, 0) AS occupied_seconds
|
||||||
|
FROM all_days_with_devices d
|
||||||
|
LEFT JOIN unoccupied_summary u
|
||||||
|
ON d.space_id = u.space_id AND d.day = u.day
|
||||||
|
)
|
||||||
|
, final_data as (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
event_date,
|
day,
|
||||||
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
device_count,
|
||||||
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
occupied_seconds,
|
||||||
FROM summed_intervals
|
ROUND(occupied_seconds / 86400.0 * 100, 2) AS occupancy_percentage
|
||||||
ORDER BY space_id, event_date)
|
FROM final_occupancy
|
||||||
|
ORDER BY space_id, day)
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO public."space-daily-occupancy-duration" (
|
INSERT INTO public."space-daily-occupancy-duration" (
|
||||||
|
@ -10,7 +10,8 @@ presence_logs AS (
|
|||||||
l.device_id,
|
l.device_id,
|
||||||
l.event_time,
|
l.event_time,
|
||||||
l.value,
|
l.value,
|
||||||
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time
|
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time,
|
||||||
|
LAG(l.value) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_value
|
||||||
FROM device d
|
FROM device d
|
||||||
JOIN "device-status-log" l ON d.uuid = l.device_id
|
JOIN "device-status-log" l ON d.uuid = l.device_id
|
||||||
JOIN product p ON p.uuid = d.product_device_uuid
|
JOIN product p ON p.uuid = d.product_device_uuid
|
||||||
@ -18,77 +19,147 @@ presence_logs AS (
|
|||||||
AND p.cat_name = 'hps'
|
AND p.cat_name = 'hps'
|
||||||
),
|
),
|
||||||
|
|
||||||
presence_intervals AS (
|
raw_absence_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
|
device_id,
|
||||||
prev_time AS start_time,
|
prev_time AS start_time,
|
||||||
event_time AS end_time
|
event_time AS end_time
|
||||||
FROM presence_logs
|
FROM presence_logs
|
||||||
WHERE value = 'none' AND prev_time IS NOT NULL
|
WHERE prev_value = 'none' AND prev_time IS NOT NULL
|
||||||
),
|
),
|
||||||
|
|
||||||
split_intervals AS (
|
absence_intervals AS (
|
||||||
|
SELECT
|
||||||
|
r.space_id,
|
||||||
|
r.device_id,
|
||||||
|
gs.day,
|
||||||
|
GREATEST(r.start_time, gs.day) AS start_time,
|
||||||
|
LEAST(r.end_time, gs.day + INTERVAL '1 day') AS end_time
|
||||||
|
FROM raw_absence_intervals r
|
||||||
|
CROSS JOIN LATERAL (
|
||||||
|
SELECT generate_series(
|
||||||
|
date_trunc('day', r.start_time),
|
||||||
|
date_trunc('day', r.end_time),
|
||||||
|
INTERVAL '1 day'
|
||||||
|
) AS day
|
||||||
|
) gs
|
||||||
|
WHERE GREATEST(r.start_time, gs.day) < LEAST(r.end_time, gs.day + INTERVAL '1 day')
|
||||||
|
),
|
||||||
|
|
||||||
|
device_counts AS (
|
||||||
|
SELECT space_id, day, COUNT(DISTINCT device_id) AS device_count
|
||||||
|
FROM absence_intervals
|
||||||
|
GROUP BY 1, 2
|
||||||
|
),
|
||||||
|
|
||||||
|
timeline AS (
|
||||||
|
SELECT
|
||||||
|
a.space_id,
|
||||||
|
a.day,
|
||||||
|
a.device_id,
|
||||||
|
a.start_time AS ts,
|
||||||
|
1 AS is_start
|
||||||
|
FROM absence_intervals a
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
a.space_id,
|
||||||
|
a.day,
|
||||||
|
a.device_id,
|
||||||
|
a.end_time AS ts,
|
||||||
|
0 AS is_start
|
||||||
|
FROM absence_intervals a
|
||||||
|
),
|
||||||
|
|
||||||
|
ordered_events AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
generate_series(
|
day,
|
||||||
date_trunc('day', start_time),
|
ts,
|
||||||
date_trunc('day', end_time),
|
is_start,
|
||||||
interval '1 day'
|
device_id,
|
||||||
)::date AS event_date,
|
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
|
||||||
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
OVER (PARTITION BY space_id, day, device_id ORDER BY ts) AS device_active
|
||||||
LEAST(end_time, date_trunc('day', end_time) + INTERVAL '1 day') AS interval_end
|
FROM timeline
|
||||||
FROM presence_intervals
|
|
||||||
),
|
),
|
||||||
|
|
||||||
ordered_intervals AS (
|
device_state_changes AS (
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
event_date,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
|
||||||
FROM split_intervals
|
|
||||||
),
|
|
||||||
|
|
||||||
grouped_intervals AS (
|
|
||||||
SELECT *,
|
|
||||||
SUM(CASE
|
|
||||||
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
|
||||||
FROM ordered_intervals
|
|
||||||
),
|
|
||||||
|
|
||||||
merged_intervals AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
event_date,
|
|
||||||
MIN(interval_start) AS merged_start,
|
|
||||||
MAX(interval_end) AS merged_end
|
|
||||||
FROM grouped_intervals
|
|
||||||
GROUP BY space_id, event_date, grp
|
|
||||||
),
|
|
||||||
|
|
||||||
summed_intervals AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
event_date,
|
day,
|
||||||
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
ts,
|
||||||
FROM merged_intervals
|
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
|
||||||
GROUP BY space_id, event_date
|
OVER (PARTITION BY space_id, day ORDER BY ts) AS net_active_devices
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT space_id, day, ts, is_start
|
||||||
|
FROM timeline
|
||||||
|
) t
|
||||||
),
|
),
|
||||||
|
|
||||||
final_data AS (
|
absence_windows AS (
|
||||||
SELECT
|
SELECT
|
||||||
s.space_id,
|
dc.space_id,
|
||||||
s.event_date,
|
dc.day,
|
||||||
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
ts AS start_time,
|
||||||
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
LEAD(ts) OVER (PARTITION BY dc.space_id, dc.day ORDER BY ts) AS end_time
|
||||||
FROM summed_intervals s
|
FROM device_state_changes dsc
|
||||||
JOIN params p
|
JOIN device_counts dc ON dc.space_id = dsc.space_id AND dc.day = dsc.day
|
||||||
|
WHERE net_active_devices = 0
|
||||||
|
),
|
||||||
|
|
||||||
|
empty_periods AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
EXTRACT(EPOCH FROM (end_time - start_time)) AS unoccupied_seconds
|
||||||
|
FROM absence_windows
|
||||||
|
WHERE end_time IS NOT NULL
|
||||||
|
),
|
||||||
|
|
||||||
|
unoccupied_summary AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
SUM(unoccupied_seconds) AS total_unoccupied_seconds
|
||||||
|
FROM empty_periods
|
||||||
|
GROUP BY space_id, day
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Include device count even for days with 0 unoccupied time
|
||||||
|
all_days_with_devices AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
DATE(event_time) AS day,
|
||||||
|
COUNT(DISTINCT device_id) AS device_count
|
||||||
|
FROM presence_logs
|
||||||
|
GROUP BY 1, 2
|
||||||
|
),
|
||||||
|
|
||||||
|
final_occupancy AS (
|
||||||
|
SELECT
|
||||||
|
d.space_id,
|
||||||
|
d.day,
|
||||||
|
d.device_count,
|
||||||
|
COALESCE(u.total_unoccupied_seconds, 0) AS unoccupied_seconds,
|
||||||
|
86400 - COALESCE(u.total_unoccupied_seconds, 0) AS occupied_seconds
|
||||||
|
FROM all_days_with_devices d
|
||||||
|
LEFT JOIN unoccupied_summary u
|
||||||
|
ON d.space_id = u.space_id AND d.day = u.day
|
||||||
|
),
|
||||||
|
|
||||||
|
final_data as(
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
device_count,
|
||||||
|
occupied_seconds,
|
||||||
|
ROUND(occupied_seconds / 86400.0 * 100, 2) AS occupancy_percentage
|
||||||
|
FROM final_occupancy s
|
||||||
|
JOIN params p
|
||||||
ON p.space_id = s.space_id
|
ON p.space_id = s.space_id
|
||||||
AND p.event_date = s.event_date
|
AND p.event_date = s.event_date
|
||||||
)
|
ORDER BY space_id, DAY)
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO public."space-daily-occupancy-duration" (
|
INSERT INTO public."space-daily-occupancy-duration" (
|
||||||
space_uuid,
|
space_uuid,
|
||||||
|
@ -12,4 +12,8 @@ AND (
|
|||||||
P.event_year IS NULL
|
P.event_year IS NULL
|
||||||
OR TO_CHAR(psdsd.event_date, 'YYYY') = TO_CHAR(P.event_year, 'YYYY')
|
OR TO_CHAR(psdsd.event_date, 'YYYY') = TO_CHAR(P.event_year, 'YYYY')
|
||||||
)
|
)
|
||||||
ORDER BY space_uuid, event_date
|
ORDER BY space_uuid, event_date
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,78 +13,156 @@ WITH presence_logs AS (
|
|||||||
AND p.cat_name = 'hps'
|
AND p.cat_name = 'hps'
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
|
-- Intervals when device was in 'absence' (between prev_time and event_time when value='none')
|
||||||
presence_intervals AS (
|
raw_absence_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
|
device_id,
|
||||||
prev_time AS start_time,
|
prev_time AS start_time,
|
||||||
event_time AS end_time
|
event_time AS end_time
|
||||||
FROM presence_logs
|
FROM presence_logs
|
||||||
WHERE value = 'none'
|
WHERE value <> 'none'
|
||||||
AND prev_value = 'presence'
|
AND prev_value = 'none'
|
||||||
AND prev_time IS NOT NULL
|
AND prev_time IS NOT NULL
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Split intervals across days
|
-- Split intervals that span multiple days into day-specific chunks
|
||||||
split_intervals AS (
|
absence_intervals AS (
|
||||||
|
SELECT
|
||||||
|
r.space_id,
|
||||||
|
r.device_id,
|
||||||
|
GREATEST(r.start_time, gs.day::timestamp) AS start_time,
|
||||||
|
LEAST(r.end_time, (gs.day + INTERVAL '1 day' - INTERVAL '1 second')::timestamp) AS end_time
|
||||||
|
FROM raw_absence_intervals r
|
||||||
|
CROSS JOIN LATERAL (
|
||||||
|
SELECT generate_series(
|
||||||
|
date_trunc('day', r.start_time),
|
||||||
|
date_trunc('day', r.end_time),
|
||||||
|
INTERVAL '1 day'
|
||||||
|
) AS day
|
||||||
|
) gs
|
||||||
|
WHERE GREATEST(r.start_time, gs.day::timestamp) < LEAST(r.end_time, (gs.day + INTERVAL '1 day')::timestamp)
|
||||||
|
),
|
||||||
|
|
||||||
|
-- FIXED: Count devices based on presence_logs OR absence_intervals
|
||||||
|
devices_per_day AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
generate_series(
|
day,
|
||||||
date_trunc('day', start_time),
|
COUNT(DISTINCT device_id) AS device_count
|
||||||
date_trunc('day', end_time),
|
FROM (
|
||||||
interval '1 day'
|
-- Devices that logged events on that day
|
||||||
)::date AS event_date,
|
SELECT space_id, DATE(event_time) AS day, device_id
|
||||||
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
FROM presence_logs
|
||||||
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
|
|
||||||
FROM presence_intervals
|
UNION
|
||||||
|
|
||||||
|
-- Devices that had absence intervals on that day
|
||||||
|
SELECT space_id, DATE(start_time) AS day, device_id
|
||||||
|
FROM absence_intervals
|
||||||
|
) combined
|
||||||
|
GROUP BY space_id, day
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Mark and group overlapping intervals per space per day
|
|
||||||
ordered_intervals AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
event_date,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
|
||||||
FROM split_intervals
|
|
||||||
),
|
|
||||||
|
|
||||||
grouped_intervals AS (
|
-- For multi-device spaces, find all time periods when ALL devices were absent
|
||||||
SELECT *,
|
multi_device_unoccupied AS (
|
||||||
SUM(CASE
|
WITH device_absence_per_day AS (
|
||||||
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
SELECT
|
||||||
ELSE 0
|
a.space_id,
|
||||||
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
DATE(a.start_time) AS day,
|
||||||
FROM ordered_intervals
|
a.device_id,
|
||||||
),
|
a.start_time,
|
||||||
|
a.end_time,
|
||||||
-- Merge overlapping intervals per group
|
d.device_count
|
||||||
merged_intervals AS (
|
FROM absence_intervals a
|
||||||
SELECT
|
JOIN devices_per_day d ON a.space_id = d.space_id AND DATE(a.start_time) = d.day
|
||||||
space_id,
|
WHERE d.device_count > 1
|
||||||
event_date,
|
),
|
||||||
MIN(interval_start) AS merged_start,
|
-- Generate time slots for each day with multiple devices
|
||||||
MAX(interval_end) AS merged_end
|
time_ranges AS (
|
||||||
FROM grouped_intervals
|
SELECT
|
||||||
GROUP BY space_id, event_date, grp
|
space_id,
|
||||||
),
|
day,
|
||||||
|
day::timestamp AS range_start,
|
||||||
-- Sum durations of merged intervals
|
(day + INTERVAL '1 day')::timestamp AS range_end,
|
||||||
summed_intervals AS (
|
device_count
|
||||||
|
FROM devices_per_day
|
||||||
|
WHERE device_count > 1
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Find all time periods when all devices were absent
|
||||||
|
all_devices_absent AS (
|
||||||
|
SELECT
|
||||||
|
t.space_id,
|
||||||
|
t.day,
|
||||||
|
t.range_start,
|
||||||
|
t.range_end,
|
||||||
|
t.device_count,
|
||||||
|
-- Find the latest start time of all devices' absence intervals
|
||||||
|
MAX(a.start_time) OVER (PARTITION BY t.space_id, t.day) AS max_start_time,
|
||||||
|
-- Find the earliest end time of all devices' absence intervals
|
||||||
|
MIN(a.end_time) OVER (PARTITION BY t.space_id, t.day) AS min_end_time
|
||||||
|
FROM time_ranges t
|
||||||
|
LEFT JOIN device_absence_per_day a ON
|
||||||
|
t.space_id = a.space_id AND
|
||||||
|
t.day = a.day
|
||||||
|
GROUP BY t.space_id, t.day, t.range_start, t.range_end, t.device_count, a.start_time, a.end_time
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Calculate total unoccupied seconds when all devices were absent
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
event_date,
|
day,
|
||||||
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
CASE
|
||||||
FROM merged_intervals
|
WHEN max_start_time IS NULL OR min_end_time IS NULL THEN 0
|
||||||
GROUP BY space_id, event_date
|
WHEN max_start_time >= min_end_time THEN 0
|
||||||
|
ELSE EXTRACT(EPOCH FROM (LEAST(min_end_time, range_end) - GREATEST(max_start_time, range_start)))
|
||||||
|
END AS unoccupied_seconds
|
||||||
|
FROM all_devices_absent
|
||||||
|
GROUP BY space_id, day, range_start, range_end, device_count, max_start_time, min_end_time
|
||||||
|
HAVING COUNT(*) = device_count -- Only include periods when all devices were absent
|
||||||
|
)
|
||||||
|
,
|
||||||
|
|
||||||
|
-- Calculate unoccupied time for spaces with single device reporting
|
||||||
|
single_device_unoccupied AS (
|
||||||
|
SELECT
|
||||||
|
a.space_id,
|
||||||
|
DATE(a.start_time) AS day,
|
||||||
|
SUM(EXTRACT(EPOCH FROM (a.end_time - a.start_time))) AS unoccupied_seconds
|
||||||
|
FROM absence_intervals a
|
||||||
|
JOIN devices_per_day d ON a.space_id = d.space_id AND DATE(a.start_time) = d.day
|
||||||
|
WHERE d.device_count = 1
|
||||||
|
GROUP BY a.space_id, DATE(a.start_time)
|
||||||
|
)
|
||||||
|
,
|
||||||
|
|
||||||
|
-- Combine results from both single and multi-device cases
|
||||||
|
combined_unoccupied AS (
|
||||||
|
SELECT space_id, day, unoccupied_seconds FROM single_device_unoccupied
|
||||||
|
UNION ALL
|
||||||
|
SELECT space_id, day, unoccupied_seconds FROM multi_device_unoccupied
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Calculate total occupied time per space per day
|
||||||
|
daily_occupancy AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
day,
|
||||||
|
-- Total seconds in day (86400) minus unoccupied seconds
|
||||||
|
86400 - COALESCE(SUM(unoccupied_seconds), 0) AS occupied_seconds
|
||||||
|
FROM combined_unoccupied
|
||||||
|
GROUP BY space_id, day
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Final output with capped seconds and percentage
|
-- Final result
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
event_date,
|
day,
|
||||||
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
occupied_seconds,
|
||||||
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
-- Also include percentage for convenience
|
||||||
FROM summed_intervals
|
ROUND((occupied_seconds / 86400.0) * 100, 2) AS occupancy_percentage
|
||||||
ORDER BY space_id, event_date;
|
FROM daily_occupancy
|
||||||
|
ORDER BY space_id, day;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user