Reputation: 2841
Let's say, I have a table that contains a list of a service outages.
The fields are: service_id, from_time, to_time.
The times are timestamp type.
The status of a service withing these intervals is "DOWN". Other times is "UP".
I'm looking for a query that would return a continuous list of time intervals representing a particular service health between a given date and now.
E.g. the table contains only one outage for the service srv1:
srv1, 11/01/2017 13:43:32, 11/01/2017 15:20:12, DOWN
Then, the result of the query from the beggining of the year should be looking like this:
srv1, 11/01/2017 15:20:12, 24/07/2017 23:55:00, UP
srv1, 11/01/2017 13:43:31, 11/01/2017 15:20:12, DOWN
srv1, 01/01/2017 00:00:00, 11/01/2017 13:43:31, UP
Assume that the outages are not overlapping.
The from_time of a following interval is equal to the to_time of a previous time interval for a service.
If a service is currently DOWN, then the to_time is equal to NULL in the outages table.
The query is for PG 9.6
Upvotes: 0
Views: 851
Reputation: 26464
You can use the lag() function to look back at a previous row.
In this case something like:
with down as (select *, lag(to_time) over (rows unbounded preceding)
as last_up
from outage order by from_time asc),
full_log as (select host, from_time, to_time, 'down'
AS as status
FROM down
UNION ALL
select host, last_up, from_time, 'up' as status
from down)
select * from full_log order by from_time asc;
Upvotes: 0
Reputation: 1269953
Basically, you want to:
The following uses union all
for this purpose:
select t.*
from t
union all
select id, coalesce(prev_to_time, '2017-01-01'::timestamp) as start_time,
start_time as to_time,
(case when status = 'DOWN' then 'UP' else 'DOWN' end) as status
from (select t.*,
lag(to_time) over (partition by id order by start_time) as prev_to_time,
lag(status) over (partition by id order by start_time) as prev_status
from t
) t
where (prev_to_time is null and start_time <> '2017-01-01'::timestamp or
prev_to_time <> start_time
) and
(prev_status is distinct from status)
union all
select id, max(end_time), now(), 'UP'
from t
group by id
having max(end_time) <> now()
order by id, start_time;
Upvotes: 1