postgres data type range - ghdrako/doc_snipets GitHub Wiki
- https://www.postgresonline.com/journal/index.php?/archives/401-Multirange-types-in-PostgreSQL-14.html#extended
- https://www.postgresql.org/docs/current/rangetypes.html
- https://www.postgresql.org/docs/current/functions-range.html
Postgres offer OVERLAPS operator who worki on two dates. No need range data typem. https://hakibenita.com/postgresql-unknown-features#find-overlapping-ranges
Range
Range types are a unique feature of PostgreSQL, managing two dimensions of data in a single column, and allowing advanced processing. Range types come with implicit quality checks. Range types:
- Time Ranges: You can use range data types to store time ranges, such as business hours, meeting times, or shifts.
- Numeric Ranges: You can use range data types to store ranges of numeric values, such as temperature, age, or income ranges.
- Geographic Ranges: You can use range data types to store geographic ranges, such as latitude and longitude ranges, or ranges of distances.
- Text Ranges: You can use range data types to store ranges of text values, such as character or string ranges.
The main example is the daterange
data type, which stores as a single value a lower and an upper bound of the range as a single value. This allows PostgreSQL to implement a concurrent safe check against overlapping ranges.
create table rates
(
currency text,
validity daterange,
rate numeric,
exclude using gist (currency with =, validity with &&)
);
insert into rates(currency, validity, rate)
select currency,
daterange(date,
lead(date) over(partition by currency
order by date),
'[)'
)
as validity,
rate
from raw.rates
order by date;
The ratę table registers the rate value for a currency and a validity period, and uses an exclusion constraint that guarantees non-overlapping validity period for any given currency:
1 exclude using gist (currency with =, validity with &&)
This expression reads: exclude any tuple where the currency is=to an existing currency in our table And where the validity is overlapping with (&&
) any existing validity in our table. This exclusion constraint is implemented in PostgreSQL using a GiST index.
Example
CREATE TABLE bookings (
room_number int,
reservation tstzrange,
EXCLUDE USING gist (room_number WITH =, reservation WITH &&)
);
INSERT INTO meeting_rooms (
room_number, reservation
) VALUES (
5, '[2022-08-20 16:00:00+00,2022-08-20 17:30:00+00]',
5, '[2022-08-20 17:30:00+00,2022-08-20 19:00:00+00]',
);
Preventing e.g. multiple concurrent reservations for a meeting room - work can be offloaded to the database with an exclusion constraint that will prevent any overlapping ranges for the same room number.
By default, GiST in PostgreSQL doesn’t support one-dimensional data types that are meant to be covered byB-tree indexes. With exclusion constraints though, it’s very interesting to extend GiST support for one-dimensional data types, and so we install the btree_gist
extension, provided in PostgreSQL contrib package.
select rate 2 from rates 3 where currency = 'Euro' 4 and validity @> date '2017-05-18';
The operator @>
reads contains, and PostgreSQL uses the exclusion constraint’s index to solve that query efficiently:
CREATE TABLE t_price_range (
id serial,
product_name text,
price numeric,
price_range daterange
);
Range formed
SELECT int4range(10, 20); -- 10 is included in the range, while 20 is not.
SELECT '[10, 19]'::int4range, '[10,20)'::int4range; -- different parantrsis but result the same
int4range | int4range
-----------+-----------
[10,20) | [10,20)
SELECT daterange('2025-10-04', '2027-05-01');
INSERT INTO t_price_range (product_name, price, price_range)
VALUES ('Apple', 1.5, '[2022-01-01, 2022-03-03]');
Querying ranges
SELECT 17 <@ '[10, 19]'::int4range;
Operator | Description | Example | Result |
---|---|---|---|
= | equal | int4range(1,5) = '[1,4]'::int4range |
t |
<> | not equal | numrange(1.1,2.2) <> numrange(1.1,2.3) |
t |
< | less than | int4range(1,10) < int4range(2,3) |
t |
> |
greater than | int4range(1,10) > int4range(1,5) |
t |
<= |
less than or equal | numrange(1.1,2.2) <= numrange(1.1,2.2) |
t |
>= |
greater than or equal | numrange(1.1,2.2) >= numrange(1.1,2.0) |
t |
@> | contains range | int4range(2,4) @> int4range(2,3) |
t |
@> | contains element | '[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestamp |
t |
<@ | range is contained by | int4range(2,4) <@ int4range(1,7) |
t |
< | element is contained by | 42 <@ int4range(1,7) |
f |
&& | overlap (have points in common) | int8range(3,7) && int8range(4,12) |
t |
<< | strictly left of | int8range(1,10) << int8range(100,110) |
t |
>> |
strictly right of | int8range(50,60) >> int8range(20,30) |
t |
Multirange
A multirange consists of one or more ranges packed together in a single column.
test=# SELECT int4multirange('{(10, 20), (30, 40)}');
int4multirange
-------------------
{[11,20),[31,40)}
(1 row)
test=# SELECT 33 <@ int4multirange('{(10, 20), (30, 40)}');
?column?
----------
t
(1 row)
test=# SELECT 25 <@ int4multirange('{(10, 20), (30, 40)}');
?column?
----------
f
(1 row)
The contains operator (<@) works normally. We can also see that those ranges are simply passed to PostgreSQL as an ordinary array. Of course, we can also check whether ranges overlap with multiranges:
test=# SELECT int4multirange('{(10, 20), (30, 40)}')
&& int4range(18, 32);
?column?
----------
t
(1 row)
test=# SELECT int4multirange('{(10, 20), (15, 30)}');
int4multirange
----------------
{[11,30)}
The database engine has figured out that those ranges are actually one. It folded those ranges into one big range.