POSTGRESQL RANGE TYPES
A range type in PostgreSQL represents a continuous interval of values as a single data type.
Instead of storing two separate columns like `start_date` and `end_date`, PostgreSQL allows you to store the interval in one logical field such as `period daterange`. A range is a first-class datatype, fully supported by operators, indexes, and constraints.
PostgreSQL range types provide:
Other RDBMS platforms can only imitate this behavior using manual patterns such as triggers, stored procedures, or complex application logic. PostgreSQL delivers the functionality natively at the database engine level.
Let’s take a practical overview and see how it implemented.
Built-in Native Range Types
|
Range Type |
Base Type |
Description |
|
int4range |
integer |
32-bit integer range |
|
int8range |
bigint |
64-bit integer range |
|
numrange |
numeric |
arbitrary precision numeric range |
|
daterange |
date |
calendar date range |
|
tsrange |
timestamp |
timestamp without timezone |
|
tstzrange |
timestamptz |
timestamp with timezone |
|
|
|
|
Basic Range Syntax
A range is defined using brackets:
[ lower , upper ]
|
Syntax |
Meaning |
|
[1,10] |
include both 1 and 10 |
|
(1,10) |
exclude both 1 and 10 |
|
[1,10) |
include 1, exclude 10 |
|
(1,10] |
exclude 1, include 10 |
|
|
|
Empty Ranges
An empty range represents a valid range value containing no elements.
SELECT 'empty'::int4range;
Unbounded Ranges
Ranges can be open-ended on either side.
SELECT '[2025-01-01,)'::daterange;
SELECT '(,2025-01-01]'::daterange;
Range Constructors
Ranges can also be built using constructor functions instead of literals.
SELECT daterange('2025-01-01', '2025-12-31', '[)');
General syntax:
range_type(lower, upper, bounds)
PostgreSQL provides rich built-in operators for range processing:
|
Operator |
Meaning |
|
&& |
overlaps |
|
@> |
contains |
|
<@ |
is contained by |
|
- |
- |
|
<< |
strictly left of |
|
>> |
strictly right of |
|
&< |
does not extend to right |
|
&> |
does not extend to left |
|
|
|
Core Functions
|
Function |
Meaning |
|
lower(range) |
returns lower bound |
|
upper(range) |
returns upper bound |
|
isempty(range) |
checks if range is empty |
|
lower_inc(range) |
is lower bound inclusive |
|
upper_inc(range) |
is upper bound inclusive |
|
range_merge(a,b) |
union of two ranges |
|
|
|
Short description of gist and sp-gist indexes, we will review then separately.
Indexing Range Types – GiST
For most range-based operations PostgreSQL uses GiST indexes, which are designed for non-standard comparisons such as overlap and containment.
CREATE INDEX ON table USING gist (range_column);
This index supports:
Alternative Index Type – SP-GiST
SP-GiST is another index type that can sometimes be more compact or efficient depending on data distribution.
CREATE INDEX ON table USING spgist (range_column);
Choice between GiST and SP-GiST depends on workload characteristics.
Real Business Scenarios
Range types are ideal for systems dealing with:
Any domain involving time intervals or numeric intervals benefits directly from range types.
PostgreSQL vs Other RDBMS – Feature Comparison
|
Feature |
PostgreSQL |
Oracle |
SQL Server |
MySQL |
|
Native range datatype |
YES |
NO |
NO |
NO |
|
Range operators |
YES |
NO |
NO |
NO |
|
Overlap operator (&&) |
YES |
NO |
NO |
NO |
|
Containment operator (@>) |
YES |
NO |
NO |
NO |
|
Range indexes |
YES |
NO |
NO |
NO |
|
Exclusion constraints |
YES |
NO |
NO |
NO |
|
Built-in range functions |
YES |
NO |
NO |
NO |
|
|
|
|
|
|
Functional Equivalents in Other Databases
To simulate PostgreSQL range behavior in Oracle, SQL Server, or MySQL, developers must store two separate columns.
Instead of:
period daterange
Other databases must use:
start_date DATE,
end_date DATE
And manually emulate overlap logic:
WHERE new.start <= existing.end
AND new.end >= existing.start
This approach is: