The particular difficulty is not to skip time intervals to an external time frame.
Assuming the next line for any given id always has the opposite status.
Using ts column name instead of recordTime :
WITH span AS ( SELECT '2014-03-01 13:00'::timestamp AS s_from -- start of time range , '2014-03-01 14:00'::timestamp AS s_to -- end of time range ) , cte AS ( SELECT id, ts, status, s_to , lead(ts, 1, s_from) OVER w AS span_start , first_value(ts) OVER w AS last_ts FROM span s JOIN tbl t ON t.ts BETWEEN s.s_from AND s.s_to WINDOW w AS (PARTITION BY id ORDER BY ts DESC) ) SELECT id, sum(time_disconnected)::text AS total_disconnected FROM ( SELECT id, ts - span_start AS time_disconnected FROM cte WHERE status = 'Connected' UNION ALL SELECT id, s_to - ts FROM cte WHERE status = 'Disconnected' AND ts = last_ts ) sub GROUP BY 1 ORDER BY 1;
Returns the intervals as requested.
Identifiers without entries in the selected time range are not displayed. You will have to request them additionally.
SQL Fiddle
Note. I entered total_disconnected into text in the script because the interval type is displayed in terrible format.
Add identifiers without recording to the selected time interval
On request in the comments.
Add to the query above (before the final ORDER BY 1 ):
... UNION ALL SELECT id, total_disconnected FROM ( SELECT DISTINCT ON (id) t.id, t.status, (s.s_to - s.s_from)::text AS total_disconnected FROM span s JOIN tbl t ON t.ts < s.s_from
SQL Fiddle
Now only identifiers without entries in or before the selected time range are not displayed.
Erwin brandstetter
source share