Menu

PostgreSQL Unique Indexes

Learn how PostgreSQL unique indexes enforce single-column and composite uniqueness, including NULLS NOT DISTINCT and partial indexes.

Updated on

PostgreSQL UNIQUE indexes are used to enforce uniqueness of values ​​in one or more columns.

Introduction to PostgreSQL UNIQUE Indexes

Only indexes of index type B-Tree can be declared as unique indexes. To create an UNIQUE index, you can use the following syntax:

CREATE UNIQUE INDEX index_name
ON table_name(column_name, [...]);

The keyword UNIQUE is used to declare an index as unique.

If a column is defined as a unique index, then the column cannot store duplicated values.

If two or more columns are defined as unique indexes, the combined values ​​in those columns cannot be duplicated.

However, you can use multiple NULL values ​​in a column with an UNIQUE index.

By default, a unique index treats NULL values as distinct, so a column can contain multiple NULL values. For composite indexes, rows with a NULL in any key column are also considered distinct by default. PostgreSQL 15 and later support NULLS NOT DISTINCT when NULL should compare as equal:

CREATE UNIQUE INDEX contacts_email_unique
ON contacts (email) NULLS NOT DISTINCT;

If the table already contains duplicate NULL values, creating this index fails until those rows are corrected.

Unique indexes can also enforce uniqueness on an expression or on only some rows by using a WHERE predicate. When you define a primary key or unique constraint on a table, PostgreSQL automatically creates the corresponding UNIQUE index.

PostgreSQL UNIQUE indexes Examples

The following statement creates a table named staff:

CREATE TABLE staff (
  id SERIAL PRIMARY KEY,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) UNIQUE
);

In this statement, the id column is a primary key and email has a unique constraint, so PostgreSQL creates two UNIQUE indexes.

To display indexes of the staff table, use the following statement:

SELECT
  tablename,
  indexname,
  indexdef
FROM
  pg_indexes
WHERE
  tablename = 'staff';
 tablename |    indexname    |                                indexdef
-----------+-----------------+-------------------------------------------------------------------------
 staff     | staff_pkey      | CREATE UNIQUE INDEX staff_pkey ON public.staff USING btree (id)
 staff     | staff_email_key | CREATE UNIQUE INDEX staff_email_key ON public.staff USING btree (email)
(2 rows)

Single Column Example

The following statement adds a column named mobile_phone to the staff table:

ALTER TABLE staff
ADD mobile_phone VARCHAR(20);

To ensure that all employees’ mobile numbers are different, you can define an UNIQUE index on this mobile_phone column as follows:

CREATE UNIQUE INDEX idx_staff_mobile_phone
ON staff(mobile_phone);

Let’s test it out.

First, insert a new row into the staff table:

INSERT INTO staff(first_name, last_name, email, mobile_phone)
VALUES ('Adam','Z','adam@sqliz.com', '13333333333');

Second, try inserting another row with the same phone number:

INSERT INTO staff(first_name, last_name, email, mobile_phone)
VALUES ('Jack','W','jack@sqliz.com', '13333333333');

Due to the duplicate mobile phone number, PostgreSQL has the following error:

ERROR:  duplicate key value violates unique constraint "idx_staff_mobile_phone"
DETAIL:  Key (mobile_phone)=(13333333333) already exists.

For a step-by-step way to locate the conflicting row and decide whether to reject, skip, or update a duplicate, see PostgreSQL Error 23505 troubleshooting. Browse the PostgreSQL error troubleshooting index for more fixes.

Multiple Column Example

The following statement will add two columns work_phone and extension to the staff table:

ALTER TABLE staff
ADD work_phone VARCHAR(20),
ADD extension VARCHAR(5);

Multiple employees can share the same work phone number. However, they cannot have the same extension number. To enforce this rule, you can define an UNIQUE index on work_phone and extension columns:

CREATE UNIQUE INDEX idx_staff_workphone
ON staff(work_phone, extension);

To test this index, first insert a row into the staff table:

INSERT INTO staff(first_name, last_name, work_phone, extension)
VALUES('Lily', 'Bush', '1234567','3564');

Second, insert another employee with the same work phone number but a different extension:

INSERT INTO staff(first_name, last_name, work_phone, extension)
VALUES('Joan', 'Doe', '1234567','3565');

The statement works because the combination of values ​​in work_phone and extension columns is unique.

Third, try inserting a row with the same value in the work_phone and extension columns in the staff table:

INSERT INTO staff(first_name, last_name, work_phone, extension)
VALUES('Tommy', 'Stark', '1234567','3565');

PostgreSQL issues the following error:

ERROR:  duplicate key value violates unique constraint "idx_staff_workphone"
DETAIL:  Key (work_phone, extension)=(1234567, 3565) already exists.

Conclusion

This article discusses the use of PostgreSQL UNIQUE indexes, which are used to enforce uniqueness of values ​​in one or more columns.