Primary Key

Learn how PostgreSQL primary keys uniquely identify rows, how to define single and composite primary keys, and how to add or drop a primary key on an existing table.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

A PostgreSQL primary key is a column or group of columns that uniquely identifies each row in a table. It enforces both a NOT NULL constraint and a UNIQUE constraint, and PostgreSQL automatically creates a unique B-tree index for it.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

A primary key uniquely identifies every row in a table. PostgreSQL enforces this by combining a NOT NULL constraint and a UNIQUE constraint on the primary key column(s), and automatically creates a unique B-tree index to support fast lookups. A table can have at most one primary key.

Defining a primary key

The most common pattern is a single auto-incrementing column defined as a column constraint:

CREATE TABLE orders (
  order_id    SERIAL PRIMARY KEY,
  customer_id VARCHAR(255) NOT NULL,
  order_date  DATE NOT NULL
);

For a composite primary key spanning multiple columns, use a table constraint:

CREATE TABLE order_items (
  order_id         INT,
  item_no          SERIAL,
  item_description VARCHAR NOT NULL,
  quantity         INTEGER NOT NULL,
  price            DEC(10, 2),
  PRIMARY KEY (order_id, item_no)
);

To assign a custom constraint name, use the CONSTRAINT clause:

CONSTRAINT pk_order_items PRIMARY KEY (order_id, item_no)

Adding a primary key to an existing table

If a table was created without a primary key, add one with ALTER TABLE ... ADD PRIMARY KEY:

CREATE TABLE products (
  product_id  INT,
  name        VARCHAR(255) NOT NULL,
  description TEXT,
  price       DEC(10, 2) NOT NULL
);

ALTER TABLE products ADD PRIMARY KEY (product_id);

To add an auto-incrementing primary key column to a table that already has data:

CREATE TABLE vendors (name VARCHAR(255));

INSERT INTO vendors (name)
VALUES ('Microsoft'), ('IBM'), ('Apple'), ('Samsung')
RETURNING *;

ALTER TABLE vendors ADD COLUMN vendor_id SERIAL PRIMARY KEY;

SELECT vendor_id, name FROM vendors;

-- Output:
--  vendor_id |   name
-- -----------+-----------
--          1 | Microsoft
--          2 | IBM
--          3 | Apple
--          4 | Samsung

Dropping a primary key

Remove a primary key constraint using ALTER TABLE ... DROP CONSTRAINT. Find the constraint name first with d table_name:

d vendors
-- Indexes:
--   "vendors_pkey" PRIMARY KEY, btree (vendor_id)

ALTER TABLE vendors DROP CONSTRAINT vendors_pkey;
-- ALTER TABLE

Dropping the constraint removes the index but does not drop the column itself.

Production tips

  • Use SERIAL or GENERATED ALWAYS AS IDENTITY for single-column primary keys to let PostgreSQL manage unique values automatically.
  • Prefer surrogate integer keys (BIGSERIAL / BIGINT) over natural keys for large tables — natural keys can change, breaking referential integrity.
  • Default PostgreSQL primary key constraint names follow the pattern table_pkey. Use the CONSTRAINT clause to assign meaningful names in migrations.
  • Adding a primary key to an existing table requires an exclusive lock. On large tables, do this during a maintenance window.
  • Foreign keys in other tables must reference a primary key or unique constraint — having a clear primary key strategy makes join queries and referential integrity simpler.

Continue in Database Constraints: Foreign Key.

Related in this section: Foreign Key · CHECK Constraint · UNIQUE Constraint

Frequently Asked Questions

What constraints does a PostgreSQL primary key enforce?

A primary key enforces two constraints simultaneously: NOT NULL (the column cannot contain a null value) and UNIQUE (no two rows can have the same value). PostgreSQL also automatically creates a unique B-tree index on the primary key column(s).

How many primary keys can a PostgreSQL table have?

A table can have at most one primary key. However, a primary key can span multiple columns (a composite primary key) to uniquely identify rows based on a combination of values.

What is the difference between SERIAL and GENERATED ALWAYS AS IDENTITY for primary keys?

Both auto-increment integers. GENERATED ALWAYS AS IDENTITY is the SQL-standard approach introduced in PostgreSQL 10 and is preferred for new tables. SERIAL is a legacy shorthand that creates a sequence implicitly. With GENERATED ALWAYS, PostgreSQL prevents manual value inserts unless you use OVERRIDING SYSTEM VALUE.

How do I find the name of a primary key constraint in PostgreSQL?

Run \d table_name in psql to see all constraints and their names, or query the information_schema: SELECT constraint_name FROM information_schema.table_constraints WHERE table_name = 'your_table' AND constraint_type = 'PRIMARY KEY';

Can I add a primary key to a table that already has duplicate or null values in the target column?

No. Adding a primary key requires that all values in the column are unique and non-null. Clean up duplicates and nulls first, then run ALTER TABLE ... ADD PRIMARY KEY.