Primary Keys
A primary key is the column, or set of columns, that uniquely identifies each row in a table. Every other part of a relational schema leans on this guarantee — foreign keys reference a primary key, ORMs assume one exists, and replication and indexing machinery use it as the natural way to address a specific row. Declaring one is usually the very first thing you do after naming a table's columns.
What PRIMARY KEY actually enforces
PRIMARY KEY is really shorthand for two constraints applied together: NOT NULL and UNIQUE. A row can never have a null primary key value, and no two rows can share the same value. PostgreSQL also automatically builds a unique B-tree index on the primary key column(s), which is why primary-key lookups are fast without any extra work.
Declaring a primary key
CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL ); -- Equivalent to writing this out explicitly: CREATE TABLE customers_explicit ( customer_id INTEGER NOT NULL, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, CONSTRAINT customers_explicit_pkey PRIMARY KEY (customer_id) );
Composite primary keys
Sometimes uniqueness only makes sense across a combination of columns rather than one alone — a classic case is a junction table for a many-to-many relationship, where the pair of foreign keys together identifies the row. A composite primary key is declared with a table-level CONSTRAINT listing more than one column.
A composite primary key on a junction table
CREATE TABLE product_tags ( product_id INTEGER NOT NULL REFERENCES products (product_id), tag_id INTEGER NOT NULL REFERENCES tags (tag_id), PRIMARY KEY (product_id, tag_id) );
This says a given (product_id, tag_id) pair can appear at most once — a product cannot be tagged with the same tag twice — while still allowing product_id and tag_id to each repeat individually across many rows.
Natural keys vs. surrogate keys
A natural key is a column that already carries real-world meaning — an email address, a national ID number, a product's SKU. A surrogate key is a value invented purely to identify the row, with no meaning outside the database, generated with SERIAL or IDENTITY (covered in depth on the SERIAL & Identity Columns page) or with a UUID.
Natural key | Surrogate key | |
|---|---|---|
Source of value | Real-world data (email, SKU, SSN) | Database-generated (identity column, UUID) |
Meaning outside the DB | Yes | None |
Stability over time | Can change (a person changes email) | Never needs to change |
Risk | Business rules evolve; the "unique" fact may stop holding | None inherent to the value itself |
Join/index performance | Depends on type (text keys are heavier) | Typically small, fixed-width, fast |
The trouble with natural keys is that they can turn out to be less permanently unique than they first seemed — companies merge and reissue SKUs, people change emails, government ID formats change. Once a natural key is baked in as a primary key and referenced by foreign keys throughout a schema, changing it later is disruptive.
PRIMARY KEY = NOT NULL + UNIQUE, plus an automatic index, on the identifying column(s).
A composite primary key spans multiple columns, common on many-to-many junction tables.
Natural keys carry real-world meaning; surrogate keys are invented purely to identify the row.
Surrogate keys (identity columns or UUIDs) are the safer default for most schemas.