range types in postgres
Nearly two years ago, Postgres got a very nice feature - range types. These are available for timestamps, numerics and integers. The problem is, that till now, I didn’t have a good example what one could do with it. But today someone gave me a quest to use it!
His problem was, that they had id ranges used by customers and they weren’t sure if they overlapped. The table looked something like this:
create table ranges( range_id serial primary key, lower_bound bigint not null, upper_bound bigint not null );
With data like this
insert into ranges(lower_bound, upper_bound) values (120000, 120500), (123000, 123750), (123750, 124000);
They had something like 40,000 rows of that kind. So this was perfect for using range type queries.
To find out, if there was an overlap, I used the following query
select * from ranges r1 join ranges r2 on int8range(r1.lower_bound, r1.upper_bound, '') && int8range(r2.lower_bound, r2.upper_bound, '') where r1.range_id != r2.range_id;
In this case, int8range takes two bigint values and converts it to a range. The string
 defines if the two values are included or excluded in the range. In this example, they are included.
The output for this query looked like the following
range_id │ lower_bound │ upper_bound │ range_id │ lower_bound │ upper_bound ──────────┼─────────────┼─────────────┼──────────┼─────────────┼───────────── 2 │ 123000 │ 123750 │ 3 │ 123750 │ 124000 3 │ 123750 │ 124000 │ 2 │ 123000 │ 123750 (2 rows) Time: 0.317 ms
But as I said, the table had 40,000 values. That means the set to filter has a size of 1.6 billion entries. The computation of the query took a very long time, so I used another nice feature of postgres - transactions.
The idea was to add a temporary index to get the computation done in a much faster time (the index is also described in the documentation).
begin; create index on ranges using gist(int8range(lower_bound, upper_bound, '')); select * from ranges r1 join ranges r2 on int8range(r1.lower_bound, r1.upper_bound, '') && int8range(r2.lower_bound, r2.upper_bound, '') where r1.range_id != r2.range_id; rollback;
The overall runtime in my case was 300ms, so the writelock wasn’t that much of a concern anymore.