简体   繁体   中英

How to construct an interval table in PostgreSQL?

I have a table data with two fields time and value .

Table Data
| time                 | value            |
|----------------------|------------------|
|"2020-05-31 17:16:48" | 132.868607594937 |
|"2020-05-31 17:31:12" | 74.1302380952381 |
|"2020-05-31 17:45:36" | 27.9773333333333 |
|"2020-05-31 18:00:00" | NULL             |
|"2020-05-31 18:14:24" | NULL             |
|"2020-05-31 18:28:48" | NULL             |
|"2020-06-01 05:16:48" | NULL             |
|"2020-06-01 05:31:12" | NULL             |
|"2020-06-01 05:45:36" | 10.3688461538462 |
|"2020-06-01 06:00:00" | 0.5295           |
|"2020-06-01 06:14:24" | 0.516052631578947|

As you can see there are rows in the table with and without values. I'd love to build a table that would list intervals when there were and were not values coming. It would look like this:

Table: Results
| startTime            | endTime               | hasValues |
|----------------------|-----------------------|-----------|
|"2020-05-31 17:16:48" | "2020-05-31 18:00:00" | true      |
|"2020-05-31 18:00:00" | "2020-06-01 05:45:36" | false     |
|"2020-06-01 05:45:36" | "2020-06-01 06:14:24" | true      |

How can I do it? I'm using Postgres 12.

This is basically a lag() and lead() :

select d.time as startime, lead(d.time, 1, max_time) over (order by time) as endtime,
       (value is not null) as has_value
from (select d.*,
             lag(value) over (order by time) as prev_value,
             max(time) over () as max_time
      from data d
     ) d
where (prev_value is null and value is not null) or
      (prev_value is not null and value is null)

Here is a db<>fiddle.

The above works because the first row has a non-NULL value. If the first row had a NULL value, then you can use:

select d.time as startime, lead(d.time, 1, max_time) over (order by time) as endtime,
       (value is not null) as has_value
from (select d.*,
             lag(value) over (order by time) as prev_value,
             max(time) over () as max_time,
             row_number() over (order by time) as seqnum
      from data d
     ) d
where seqnum = 1 or
      (prev_value is null and value is not null) or
      (prev_value is not null and value is null)

Or more concisely as:

select d.time as startime, lead(d.time, 1, max_time) over (order by time) as endtime,
       has_value
from (select d.*, v.has_value,
             lag(v.has_value) over (order by d.time) as prev_has_value,
             max(d.time) over () as max_time
      from data d cross join lateral
           (values (d.value is not null)) v(has_value)
     ) d
where prev_has_value is null or
      (not prev_has_value and has_value) or
      (prev_has_value and not has_value);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM