I had some doubts about this basic but important issue, so I decided to learn by example.

Let's create test table master with two columns, con_id with unique constraint and ind_id indexed by unique index.

create table master (
    con_id integer unique,
    ind_id integer
);
create unique index master_unique_idx on master (ind_id);

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_unique_idx" UNIQUE, btree (ind_id)

In table description (\d in psql) you can tell unique constraint from unique index.

Uniqueness

Let's check uniqueness, just in case.

test=# insert into master values (0, 0);
INSERT 0 1
test=# insert into master values (0, 1);
ERROR:  duplicate key value violates unique constraint "master_con_id_key"
DETAIL:  Key (con_id)=(0) already exists.
test=# insert into master values (1, 0);
ERROR:  duplicate key value violates unique constraint "master_unique_idx"
DETAIL:  Key (ind_id)=(0) already exists.
test=#

It works as expected!

Foreign keys

Now we'll define detail table with two foreign keys referencing to our two columns in master.

create table detail (
    con_id integer,
    ind_id integer,
    constraint detail_fk1 foreign key (con_id) references master(con_id),
    constraint detail_fk2 foreign key (ind_id) references master(ind_id)
);

    Table "public.detail"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Foreign-key constraints:
    "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Well, no errors. Let's make sure it works.

test=# insert into detail values (0, 0);
INSERT 0 1
test=# insert into detail values (1, 0);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk1"
DETAIL:  Key (con_id)=(1) is not present in table "master".
test=# insert into detail values (0, 1);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk2"
DETAIL:  Key (ind_id)=(1) is not present in table "master".
test=#

Both columns can be referenced in foreign keys.

Constraint using index

You can add table constraint using existing unique index.

alter table master add constraint master_ind_id_key unique using index master_unique_idx;

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_ind_id_key" UNIQUE CONSTRAINT, btree (ind_id)
Referenced by:
    TABLE "detail" CONSTRAINT "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    TABLE "detail" CONSTRAINT "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Now there is no difference between column constraints description.

Partial indexes

In table constraint declaration you cannot create partial indexes. It comes directly from the definition of create table .... In unique index declaration you can set WHERE clause to create partial index. You can also create index on expression (not only on column) and define some other parameters (collation, sort order, NULLs placement).

You cannot add table constraint using partial index.

alter table master add column part_id integer;
create unique index master_partial_idx on master (part_id) where part_id is not null;

alter table master add constraint master_part_id_key unique using index master_partial_idx;
ERROR:  "master_partial_idx" is a partial index
LINE 1: alter table master add constraint master_part_id_key unique ...
                               ^
DETAIL:  Cannot create a primary key or unique constraint using such an index.
Answer from klin on Stack Overflow
๐ŸŒ
PostgreSQL
postgresql.org โ€บ docs โ€บ current โ€บ indexes-unique.html
PostgreSQL: Documentation: 18: 11.6. Unique Indexes
May 14, 2026 - ... Unsupported versions: 13 / ... of the combined values of more than one column. CREATE UNIQUE INDEX name ON table (column [, ...]) [ NULLS [ NOT ] DISTINCT ];...
๐ŸŒ
Neon
neon.com โ€บ postgresql โ€บ indexes โ€บ unique-index
PostgreSQL UNIQUE Index
First, specify the index name in the CREATE UNIQUE INDEX statement. Second, provide the name of the table along with a list of indexed columns in the ON clause. Third, the NULLS NOT DISTINCT option treats nulls as equal, whereas NULLS DISTINCT ...
Discussions

sql - Postgres unique constraint vs index - Stack Overflow
The use of indexes to enforce unique constraints could be considered an implementation detail that should not be accessed directly. (Edit: this note was removed from the manual with Postgres 9.5.) Is it only a matter of good style? What are practical consequences of choice one of these variants (e.g. in performance)? ... The (only) practical difference is that you can create ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
postgresql - Postgres: Create unique index with conditional - Database Administrators Stack Exchange
I have a table in Postgres with records of type [event_id, user_id, status]. The user is scoped per event, so I have a unique index on [event_id, user_id] so that a user has a unique status. The st... More on dba.stackexchange.com
๐ŸŒ dba.stackexchange.com
September 27, 2022
postgresql - Skip (or speedup) postgres unique index creation if I know beforehand that it's unique - Database Administrators Stack Exchange
Is there a way to tell postgres to skip that index validation, because "I swear to god, it's unique" (x-y problem) maybe I miss something and there is a simplier way to achieve this without index creation. More on dba.stackexchange.com
๐ŸŒ dba.stackexchange.com
December 7, 2022
how to create non unique index on postgresql ?
CREATE INDEX? More on reddit.com
๐ŸŒ r/SQL
3
2
March 2, 2023
๐ŸŒ
PostgreSQL
postgresql.org โ€บ docs โ€บ current โ€บ sql-createindex.html
PostgreSQL: Documentation: 18: CREATE INDEX
May 14, 2026 - Whether there can be multiple key columns is independent of whether INCLUDE columns can be added to the index. Indexes can have up to 32 columns, including INCLUDE columns. (This limit can be altered when building PostgreSQL.) Only B-tree currently supports unique indexes.
๐ŸŒ
Medium
medium.com โ€บ flatiron-engineering โ€บ uniqueness-in-postgresql-constraints-versus-indexes-4cf957a472fd
Uniqueness in PostgreSQL: Constraints versus Indexes | by Lauren Ellsworth | Flatiron Engineering | Medium
December 30, 2017 - If you are adding a uniqueness ... an index can be created concurrently while a constraint cannot. Should you choose a concurrent index in this case, you can add a unique constraint that depends on that index, effectively doing manually what PostgreSQL would have done ...
Top answer
1 of 13
243

I had some doubts about this basic but important issue, so I decided to learn by example.

Let's create test table master with two columns, con_id with unique constraint and ind_id indexed by unique index.

create table master (
    con_id integer unique,
    ind_id integer
);
create unique index master_unique_idx on master (ind_id);

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_unique_idx" UNIQUE, btree (ind_id)

In table description (\d in psql) you can tell unique constraint from unique index.

Uniqueness

Let's check uniqueness, just in case.

test=# insert into master values (0, 0);
INSERT 0 1
test=# insert into master values (0, 1);
ERROR:  duplicate key value violates unique constraint "master_con_id_key"
DETAIL:  Key (con_id)=(0) already exists.
test=# insert into master values (1, 0);
ERROR:  duplicate key value violates unique constraint "master_unique_idx"
DETAIL:  Key (ind_id)=(0) already exists.
test=#

It works as expected!

Foreign keys

Now we'll define detail table with two foreign keys referencing to our two columns in master.

create table detail (
    con_id integer,
    ind_id integer,
    constraint detail_fk1 foreign key (con_id) references master(con_id),
    constraint detail_fk2 foreign key (ind_id) references master(ind_id)
);

    Table "public.detail"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Foreign-key constraints:
    "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Well, no errors. Let's make sure it works.

test=# insert into detail values (0, 0);
INSERT 0 1
test=# insert into detail values (1, 0);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk1"
DETAIL:  Key (con_id)=(1) is not present in table "master".
test=# insert into detail values (0, 1);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk2"
DETAIL:  Key (ind_id)=(1) is not present in table "master".
test=#

Both columns can be referenced in foreign keys.

Constraint using index

You can add table constraint using existing unique index.

alter table master add constraint master_ind_id_key unique using index master_unique_idx;

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_ind_id_key" UNIQUE CONSTRAINT, btree (ind_id)
Referenced by:
    TABLE "detail" CONSTRAINT "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    TABLE "detail" CONSTRAINT "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Now there is no difference between column constraints description.

Partial indexes

In table constraint declaration you cannot create partial indexes. It comes directly from the definition of create table .... In unique index declaration you can set WHERE clause to create partial index. You can also create index on expression (not only on column) and define some other parameters (collation, sort order, NULLs placement).

You cannot add table constraint using partial index.

alter table master add column part_id integer;
create unique index master_partial_idx on master (part_id) where part_id is not null;

alter table master add constraint master_part_id_key unique using index master_partial_idx;
ERROR:  "master_partial_idx" is a partial index
LINE 1: alter table master add constraint master_part_id_key unique ...
                               ^
DETAIL:  Cannot create a primary key or unique constraint using such an index.
2 of 13
58

One more advantage of using UNIQUE INDEX vs. UNIQUE CONSTRAINT is that you can easily DROP/CREATE an index CONCURRENTLY, whereas with a constraint you can't.

Find elsewhere
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ postgresql โ€บ postgresql-unique-index
PostgreSQL - UNIQUE Index - GeeksforGeeks
July 15, 2025 - The following statement creates a table called employees. The 'employee_id' column is defined as the primary key. The 'email' column has a unique constraint, ensuring that no two employees can have the same email address. Therefore, PostgreSQL created two UNIQUE indexes, one for each column.
๐ŸŒ
Heroku Dev Center
devcenter.heroku.com โ€บ articles โ€บ postgresql-indexes
Efficient Use of PostgreSQL Indexes | Heroku Dev Center
You can think of unique indexes as lower level since you canโ€™t create expression indexes and partial indexes as unique constraints. Even partial unique indexes on expressions are possible. The Postgres query planner can combine and use multiple single-column indexes in a multi-column query ...
๐ŸŒ
OneUptime
oneuptime.com โ€บ home โ€บ blog โ€บ how to create unique constraints with null values in postgresql
How to Create Unique Constraints with NULL Values in PostgreSQL
January 25, 2026 - -- PostgreSQL 15+ solution CREATE ... appear in real data CREATE UNIQUE INDEX idx_variants_unique ON product_variants (product_id, (COALESCE(color, '___NULL___')), (COALESCE(size, '___NULL___')));...
๐ŸŒ
Flatiron
resources.flatiron.com โ€บ flatiron-stories โ€บ uniqueness-in-postgresql-constraints-versus-indexes
Uniqueness in PostgreSQL: Constraints versus Indexes
March 29, 2024 - CREATE UNIQUE INDEX CONCURRENTLY examples_new_col_idx ON examples (new_col); ALTER TABLE examples ADD CONSTRAINT examples_unique_constraint USING INDEX examples_new_col_idx; PostgreSQL Documentation - Alter Table
๐ŸŒ
Medium
medium.com โ€บ dovetail-engineering โ€บ how-to-safely-create-unique-indexes-in-postgresql-e35980e6beb5
How to safely create unique indexes in PostgreSQL | by Dovetail Engineering | Dovetail Engineering | Medium
May 23, 2023 - CREATE UNIQUE INDEX reference_count_unique_index ON reference_count (document_id, object_id); I wish it were that simple! In a production environment, this approach can lead to delayed user requests or, at worst, request timeouts and increased ...
๐ŸŒ
Medium
medium.com โ€บ little-programming-joys โ€บ unique-partial-indexes-with-postgresql-86e137905c12
Unique partial indexes with PostgreSQL | by Anderson Dias | Little programming joys | Medium
September 10, 2016 - Itโ€™s an index, like any other, with a scope, and indexes can be uniqueโ€ฆ ;) ... CREATE UNIQUE INDEX unique_enabled_user_toggle ON toggles (user_id, type) WHERE disabled_at IS NULL;
๐ŸŒ
PostgreSQL Tutorial
pgtutorial.com โ€บ home โ€บ postgresql tutorial โ€บ postgresql unique index
PostgreSQL Unique Index
March 7, 2025 - A unique index ensures values in ... to insert a value already in a column with a unique index. To create a unique index, you use the CREATE UNIQUE INDEX statement:...
๐ŸŒ
PostgreSQL
postgresql.org โ€บ docs โ€บ current โ€บ index-unique-checks.html
PostgreSQL: Documentation: 18: 63.5. Index Uniqueness Checks
May 14, 2026 - UNIQUE_CHECK_YES indicates that this is a non-deferrable unique index, and the uniqueness check must be done immediately, as described above. UNIQUE_CHECK_PARTIAL indicates that the unique constraint is deferrable. PostgreSQL will use this mode to insert each row's index entry.
๐ŸŒ
Render
render.com โ€บ docs โ€บ deploys
Deploying on Render โ€“ Render Docs
Create and connect to a Render-managed datastore (Render Postgres or Key Value).
๐ŸŒ
FastAPI
fastapi.tiangolo.com โ€บ tutorial โ€บ sql-databases
SQL (Relational) Databases - FastAPI
# Code above omitted ๐Ÿ‘† class HeroBase(SQLModel): name: str = Field(index=True) age: int | None = Field(default=None, index=True) # Code below omitted ๐Ÿ‘‡ ... from typing import Annotated from fastapi import Depends, FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): name: str = Field(index=True) age: int | None = Field(default=None, index=True) class Hero(HeroBase, table=True): id: int | None = Field(default=None, primary_key=True) secret_name: str class HeroPublic(HeroBase): id: int class HeroCreate(HeroBase): secre
๐ŸŒ
Stack Exchange
dba.stackexchange.com โ€บ questions โ€บ 320691 โ€บ skip-or-speedup-postgres-unique-index-creation-if-i-know-beforehand-that-its
postgresql - Skip (or speedup) postgres unique index creation if I know beforehand that it's unique - Database Administrators Stack Exchange
December 7, 2022 - -- CURRENT STATE CREATE TABLE test ... integer CONSTRAINT test_pkey PRIMARY KEY (id) ); CREATE UNIQUE INDEX idx1 ON test (client_id, order_id, typ) WHERE (typ in ['foo', 'bar']); -- what's even more interesting, this non-unique index ...
๐ŸŒ
Ai2sql
builder.ai2sql.io โ€บ blog โ€บ how-to-create-index-postgresql
PostgreSQL CREATE INDEX (2026): B-tree, GIN, Partial + When Each Wins
March 12, 2026 - -- Unique index: CREATE UNIQUE INDEX idx_users_email_unique ON users (email); -- Multi-column unique: CREATE UNIQUE INDEX idx_team_members ON team_members (team_id, user_id); Tip: In PostgreSQL, a UNIQUE constraint automatically creates a unique ...
๐ŸŒ
Medium
medium.com โ€บ @_ketanbhatt โ€บ postgres-recreating-indexes-supporting-unique-foreign-key-and-primary-key-constraints-572a1f2dfa5b
Postgres: Recreating Indexes supporting Unique, Foreign Key and Primary Key Constraints | by Ketan Bhatt | Medium
October 10, 2019 - CREATE UNIQUE INDEX CONCURRENTLY new_uniq_idx ON my_sweet_table USING btree (col_a, col_b); Now, we want the constraint to use this new index. For that, we drop the original constraint, and add a new unique constraint that uses our new index.