Introduction
A range is a sequential series of data. In a PostgreSQL database server, you can store a range of values using a mathematical interval notation that implements square brackets and parenthesis to define lower and upper bounds of all possible data elements. For instance, in a loan processing application, you can use the range datatype to store the following data:
-
The minimum and maximum allowed principal for each loan product (For example, you can represent a loan with a minimum allowed principal of $5,000 and a maximum principal of $100,000 as
[5000, 100000]
). -
The fluctuating annual interest rate of loans. This depends on the credit score of borrowers and the economy. (For example, the
[8.5, 18.6]
value represents a loan with an annual interest rate that varies between8.5% and 18.6%
). -
The differing loan term (or number of installments) depending on the customers' capability. (For example, if a customer can pay a loan from 6 to 36 months, you can use a value of
[6, 36]
to denote the information).
Without the PostgreSQL range datatype, you need two different columns to store the lower and upper bounds of all the above scenarios. This guide explains how to implement and use the PostgreSQL range data type, run common operators, and use the different functions on a Ubuntu 20.04 server.
Prerequisites
Before you begin, be sure to:
Understanding the PostgreSQL Range Subtypes
The PostgreSQL server provides different range subtypes you can use to meet your needs. To check the availability of these subtypes in your database server, follow the steps below:
-
Log in to PostgreSQL as the user
postgres
.$ sudo -u postgres psql
Enter the correct user password and press enter to continue.
-
Enter the following command to retrieve available range subtypes.
postgres=# SELECT oid, typname FROM pg_type WHERE typname ~ 'range';
Output:
oid | typname -------+------------ 3904 | int4range 3926 | int8range 3906 | numrange 3908 | tsrange 3910 | tstzrange 3912 | daterange ...
Each of the PostgreSQL range subtypes represents:
-
int4range
: A range of 4 bytesinteger
. -
int8range
: A range of 8 bytesinteger
. -
numrange
: A range ofnumeric
values. -
tsrange
: A range of timestamps data without timezones. -
tstzrange
: A range of timestamps data with timezones. -
daterange
: A range of dates.
-
After understanding the different PostgreSQL range subtypes, create a sample database.
Create a Sample PostgreSQL Database
The PostgreSQL range subtypes explained in the previous section are useful. This section describes how to use most of these range subtypes with real-life examples as explained below.
-
Create a
sample_company
database.postgres=# CREATE DATABASE sample_company;
-
Switch to the new
sample_company
database.postgres=# \c sample_company;
-
Create a sample
loans_products
table.sample_company=# CREATE TABLE loan_products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(50), principal NUMRANGE, apr NUMRANGE, no_of_installments INT4RANGE, validity TSRANGE );
This table stores information about the available loan products in sample bank applications. The
application_id
column uniquely identifies each record in the table. TheSERIAL
keyword instructs PostgreSQL to create auto-incremented records to avoid typing theproduct_ids
manually.The
product_name
column identifies the various loan products in the bank. Theprincipal
andapr
(annual interest rate) columns use theNUMRANGE
data type, this datatype allows you to store the lower and upper bound limits of the allowed principal and interest for each loan.The
no_of_installments
column must contain a range of integers that define the repayment period of a loan in months. Then, thevalidity
column stores a range of timestamps to check whether a loan is available when customers submit their applications. -
Before populating data to the table, examine the mathematical interval notation below. A square bracket denotes an inclusive bound that includes the boundary points in the range. A round parenthesis represents an exclusive bound that excludes the boundary points from the range.
-
[
Starts a range and includes the starting value in the range. -
]
Ends a range and includes the end value in the range. -
(
Start a range and excludes the starting value from the range. -
)
Ends a range and excludes the end value from the range.
For example:
-
[a,b] - Includes all values between
a
andb
in the range. -
(a,b] - Includes all values ​​between
a
andb
, excludesa
, and includesb
in the range. -
[a,b) - Includes all values between
a
andb
, excludesb
but includesa
in the range. -
(a,b) - Includes all values between
a
andb
and excludes botha
andb
from the range.
-
-
Populate the
loan_products
table as below.sample_company=# INSERT INTO loan_products (product_name, principal, apr, no_of_installments, validity) VALUES ('EMERGENCY LOAN', '[1000,5000]', '[8,12]', '[3,12]', '[2023-01-01 00:00, 2023-12-31 23:59]'); INSERT INTO loan_products (product_name, principal, apr, no_of_installments, validity) VALUES ('DEVELOPMENT LOAN', '[3000,10000]', '[5,10]', '[12,36]', '[2023-01-01 00:00, 2023-05-31 23:59]'); INSERT INTO loan_products (product_name, principal, apr, no_of_installments, validity) VALUES ('STUDENT LOAN', '[500,1000]', '[4,7]', '[1,3]', '[2023-01-01 00:00, 2023-12-31 23:59]');
Output:
... INSERT 0 1
-
Query the
loan_products
table data.sample_company=# SELECT product_id, product_name, principal, apr, no_of_installments, validity FROM loan_products;
Output:
product_id | product_name | principal | apr | no_of_installments | validity ------------+------------------+--------------+--------+--------------------+----------------------------------------------- 1 | EMERGENCY LOAN | [1000,5000] | [8,12] | [3,13) | ["2023-01-01 00:00:00","2023-12-31 23:59:00"] 2 | DEVELOPMENT LOAN | [3000,10000] | [5,10] | [12,37) | ["2023-01-01 00:00:00","2023-05-31 23:59:00"] 3 | STUDENT LOAN | [500,1000] | [4,7] | [1,4) | ["2023-01-01 00:00:00","2023-12-31 23:59:00"] (3 rows)
With the sample database in place, proceed to learn how you can use basic PostgreSQL range operators and functions.
Basic Range Datatype Operators and Functions
You can achieve the full power of the PostgreSQL range datatype using inbuilt operators and functions. Below are the most common operators and functions.
-
@>
Checks if a range contains another range or element. -
lower()
returns the lower bound of a given range. -
upper()
returns the upper bound of a given range.
To test the above operator and functions, run the following examples:
-
Select all loan products that allow a customer to borrow $500 using the
@>
operator.sample_company=# SELECT product_id, product_name, principal FROM loan_products WHERE principal @> '500'::NUMERIC;
Output:
product_id | product_name | principal ------------+--------------+------------ 3 | STUDENT LOAN | [500,1000] (1 row)
-
List the minimum and maximum principal allowed for each loan product using the
lower()
andupper()
functions.sample_company=# SELECT product_id, product_name, lower(principal) as min_principal, upper(principal) as max_principal FROM loan_products;
Output:
product_id | product_name | min_principal | max_principal ------------+------------------+---------------+--------------- 1 | EMERGENCY LOAN | 1000 | 5000 2 | DEVELOPMENT LOAN | 3000 | 10000 3 | STUDENT LOAN | 500 | 1000 (3 rows)
-
Select all loan products available on the first day of June 2023 using the
@>
operator.sample_company=# SELECT product_id, product_name, principal, validity FROM loan_products WHERE validity @> '2023-06-01'::timestamp;
Output:
product_id | product_name | principal | validity ------------+----------------+-------------+----------------------------------------------- 1 | EMERGENCY LOAN | [1000,5000] | ["2023-01-01 00:00:00","2023-12-31 23:59:00"] 3 | STUDENT LOAN | [500,1000] | ["2023-01-01 00:00:00","2023-12-31 23:59:00"] (2 rows)
Indexing
The examples in this guide only use a few records. For better query speed as your database grows, add a Generalized Search Tree-based Index (GIST) on the range columns. For instance, to create a principal
column index, run the following command.
sample_company=# CREATE INDEX principal_idx ON loan_products USING GIST (principal);
Output:
CREATE INDEX
Conclusion
In this guide, you implemented the PostgreSQL range data type on a Ubuntu 20.04 server. You have created a sample table, populated the range columns using a mathematical interval notation, and run queries to test PostgreSQL range operators and functions.
Next Steps
To leverage PostgreSQL functions, please visit the following resources.