9.4. Primary Keys
Table of Contents
Why Primary Keys Matter
In every relational database table, you need a reliable way to talk about one specific row. That is what a primary key is for.
A primary key is a column, or a combination of columns, that uniquely identifies each row in a table. Without a primary key, it is hard to update, delete, or relate data correctly.
Primary key rule: Every table should have a primary key, and primary key values must be unique and non‑NULL.
Examples of things that naturally have unique identifiers:
| Entity | Real world unique identifier | Possible primary key column |
|---|---|---|
| User | Email, username, internal ID | id or email |
| Product | Barcode, SKU, internal product ID | id or sku |
| Order | Order number | id |
| Country | ISO code (e.g. "US", "FR") | code |
In practice, you will very often use an integer column named id as the primary key.
Properties of a Primary Key
A primary key has a few strict properties.
Uniqueness
Each primary key value must be unique within the table.
If id is the primary key in a users table, and you have these rows:
| id | |
|---|---|
| 1 | alice@example.com |
| 2 | bob@example.com |
Then you cannot insert another row with id = 1 or id = 2.
In SQL, the database enforces this:
CREATE TABLE users (
id INT PRIMARY KEY,
email TEXT
);
-- OK
INSERT INTO users (id, email)
VALUES (1, 'alice@example.com');
-- Error: duplicate key value violates unique constraint
INSERT INTO users (id, email)
VALUES (1, 'another@example.com');The primary key automatically has a unique constraint.
Non‑NULL
Primary key columns cannot contain NULL. NULL means "unknown" or "missing". A key that is unknown cannot uniquely identify anything.
CREATE TABLE products (
id INT PRIMARY KEY,
name TEXT
);
-- Error: null value in column "id" violates not-null constraint
INSERT INTO products (id, name)
VALUES (NULL, 'Some Product');
If you try to insert NULL in a primary key column, the database will reject it.
Stability
Primary key values should rarely change. If you change them, you can break relationships with other tables that refer to that key.
For example, if users.id is used in an orders table as a foreign key, and you change users.id from 5 to 10, every row in orders that refers to user 5 might break.
So, as a rule, prefer keys that:
- Do not change over time.
- Are assigned once when the row is created.
- Are simple to compare and index.
Good practice: Choose primary keys that are stable, simple, and never reused for a different row.
Single‑Column vs Composite Primary Keys
A primary key can use one column, or multiple columns together.
Single‑Column Primary Keys
This is the most common pattern. One column, often named id, is declared as the primary key.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL
);Explanation:
idis the primary key.- Each row has a unique
id. - You can reference that row from other tables using this
id.
Benefits:
- Simple to understand.
- Easy to use in joins.
- Short values, efficient indexes.
You will see this pattern almost everywhere.
Composite Primary Keys
A composite primary key uses multiple columns together to uniquely identify a row.
Example: A table that stores a student's grade for a course.
CREATE TABLE course_grades (
student_id INT NOT NULL,
course_id INT NOT NULL,
grade INT,
PRIMARY KEY (student_id, course_id)
);
Here, the primary key is (student_id, course_id):
- A student can have only one row per course.
student_idalone is not unique.course_idalone is not unique.- The combination of
(student_id, course_id)is unique.
Table example:
| student_id | course_id | grade |
|---|---|---|
| 1 | 101 | 90 |
| 1 | 102 | 85 |
| 2 | 101 | 88 |
You cannot insert another row with (student_id = 1, course_id = 101).
Composite keys are useful when:
- The unique identity really is a combination of fields, such as
(user_id, role_id),(order_id, product_id),(country_code, language_code). - You want to prevent duplicates based on these fields without adding a separate
idcolumn.
However, composite keys can make joins and foreign keys more complex, because every related table must include all columns of the composite key.
Natural Keys vs Surrogate Keys
There are two main strategies for choosing what column to use as your primary key.
Natural Keys
A natural key is a primary key that comes from real world data and already uniquely identifies an entity.
Examples:
| Table | Natural key candidate |
|---|---|
| countries | code (e.g. "US", "FR") |
| users | email |
| products | sku |
Example using email as primary key:
CREATE TABLE users (
email TEXT PRIMARY KEY,
name TEXT NOT NULL
);Pros:
- No extra column is needed.
- Directly meaningful to humans.
Cons:
- Real world identifiers can change. For example, users change emails.
- Natural keys can be long strings, which affect index size and performance.
- You might want to allow duplicates in future (for some identifiers), which could create trouble.
Because natural keys can change, using them as primary keys can create maintenance problems later.
Surrogate Keys
A surrogate key is a key that has no meaning in the real world. It only exists to identify rows inside the database.
Common surrogate keys:
- Auto incrementing integers:
id SERIALorid BIGSERIAL. - UUIDs:
id UUID.
Example:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL
);Here:
idis a surrogate primary key.emailis unique, but not the primary key.
Pros:
- Simple and stable.
idnever changes. - Short integers are fast to index and join.
- You can change real world identifiers, like
email, without touching relationships.
Cons:
- Less meaningful outside the database.
id = 42means nothing to the user. - You still need additional unique constraints for fields like
emailorusername.
In most backend applications, especially larger ones, you will use surrogate keys for primary keys and add unique constraints on natural identifiers when needed.
Very common pattern: Use a surrogate primary key (like id SERIAL), plus UNIQUE constraints on any real world identifiers you care about, such as email or username.
Creating Primary Keys in SQL
Different SQL dialects have slightly different syntax, but the ideas are the same. Here are common patterns you will use.
Defining a Single‑Column Primary Key
You can define the primary key directly on a column.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL
);
Here, PRIMARY KEY does two things:
- Makes
idNOT NULL. - Creates a unique index on
id.
Alternatively, you can define it as a separate constraint:
CREATE TABLE users (
id INT NOT NULL,
email TEXT NOT NULL,
PRIMARY KEY (id)
);
Both definitions mean the same: id is the primary key.
Example Insert and Select
INSERT INTO users (email)
VALUES ('alice@example.com'),
('bob@example.com');
SELECT * FROM users;
If id is SERIAL, the database will generate values like 1, 2, 3 automatically.
Defining a Composite Primary Key
To define a composite key, list multiple columns.
CREATE TABLE enrollment (
student_id INT NOT NULL,
course_id INT NOT NULL,
enrolled_at TIMESTAMP NOT NULL,
PRIMARY KEY (student_id, course_id)
);- The pair
(student_id, course_id)must be unique. - The database will prevent duplicates.
Attempting to insert a duplicate pair:
INSERT INTO enrollment (student_id, course_id, enrolled_at)
VALUES (1, 101, NOW());
-- This will fail, because (1, 101) already exists
INSERT INTO enrollment (student_id, course_id, enrolled_at)
VALUES (1, 101, NOW());The database will raise an error about duplicate key value.
Auto‑Incrementing Primary Keys
Very often, you want the database to automatically generate a new integer ID for each row. Different databases have different keywords, but the concept is the same.
Typical Auto‑Increment Syntax
Examples:
| Database | Example syntax |
|---|---|
| PostgreSQL | id SERIAL PRIMARY KEY |
| MySQL | id INT AUTO_INCREMENT PRIMARY KEY |
| SQLite | id INTEGER PRIMARY KEY AUTOINCREMENT |
Example in PostgreSQL style:
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL
);
INSERT INTO posts (title, content)
VALUES ('First post', 'Hello world!');Resulting table:
| id | title | content |
|---|---|---|
| 1 | First post | Hello world! |
You did not provide id. The database filled it for you.
This is often called a surrogate key with auto increment.
Why Auto‑Increment Is Popular
- You do not need to generate IDs in your code.
- IDs are short, numeric, and ordered by insertion time.
- It makes debugging easier. For example, "the bug is in user with id 37".
Potential issue: In distributed systems or when multiple databases are merged, plain auto increment IDs can clash across databases. In such cases, UUIDs or other strategies may be used, but that goes beyond basic primary keys.
Primary Keys and Indexes
When you create a primary key, the database automatically creates an index on that key.
An index is a structure that makes lookups by the key faster.
Example:
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
The database creates an index on id. When you run:
SELECT * FROM customers WHERE id = 123;The query can use the index to find the row quickly.
You can think of the primary key index as the table's "main index" used by the storage engine.
You do not need to manually add an index on the primary key column. It is already there.
Working With Primary Keys in Queries
Primary keys are used very often in everyday queries.
Selecting by Primary Key
SELECT * FROM users WHERE id = 5;This is the typical way you fetch one record in an API endpoint like:
GET /users/5
Your backend will translate that 5 into a query like the one above.
Updating by Primary Key
UPDATE users
SET email = 'new-email@example.com'
WHERE id = 5;
Because id is unique, this query affects at most one row.
If you mistakenly write a condition that matches multiple rows, you can accidentally update too much data. Using the primary key in the WHERE clause helps avoid that.
Deleting by Primary Key
DELETE FROM users
WHERE id = 5;
Again, id ensures you only delete the intended row.
Primary Keys and Relationships
Primary keys are closely connected to foreign keys and relationships, which are covered in another chapter. Here is the basic idea.
- A primary key identifies a row in one table.
- Another table can store that key in a column, and declare it as a foreign key.
- This creates a relationship like "each order belongs to one user".
Example of a simple relationship:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
total NUMERIC(10, 2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);Here:
users.idis a primary key.orders.user_idis a foreign key that must match a value inusers.id.
You can then join tables using the primary key:
SELECT o.id AS order_id, u.email, o.total
FROM orders o
JOIN users u ON o.user_id = u.id;Choosing a Good Primary Key
When you design tables, you need to decide what the primary key will be. Use these guidelines.
Prefer Surrogate Keys for Most Application Tables
For typical backend systems, a pattern like this is common:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
username TEXT NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);Reasons:
idis simple and stable.emailandusernamecan change (with some rules).- You can add or change unique rules later without affecting the primary key.
Use Natural Keys When They Are Truly Stable
For small reference tables where codes never change, natural keys can be fine.
Example:
CREATE TABLE countries (
code CHAR(2) PRIMARY KEY, -- ISO code, e.g. 'US', 'FR'
name TEXT NOT NULL
);
Here, code is well defined and stable. Adding an extra id might not be necessary.
Another example:
CREATE TABLE languages (
code CHAR(2) PRIMARY KEY,
name TEXT NOT NULL
);Use Composite Keys for Many‑to‑Many Join Tables
Join tables that connect two entities often use composite keys.
Example: Users can subscribe to many newsletters, and each newsletter has many users.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE newsletters (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE user_newsletters (
user_id INT NOT NULL,
newsletter_id INT NOT NULL,
subscribed_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, newsletter_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (newsletter_id) REFERENCES newsletters(id)
);
The composite primary key (user_id, newsletter_id):
- Ensures a user cannot subscribe to the same newsletter twice.
- Makes logical sense: the combination uniquely identifies the subscription.
Some teams still add a surrogate id column even here, but for many join tables, a composite primary key is perfectly fine and very clear.
Common Mistakes With Primary Keys
Understanding common mistakes helps you avoid problems later.
Not Having a Primary Key
Some people skip primary keys to "keep it simple". This makes it hard to:
- Uniquely identify rows.
- Build relationships.
- Update or delete specific rows safely.
Avoid tables without primary keys in real applications.
Using Unstable Data as a Primary Key
Using values that can change frequently as the primary key causes trouble.
Bad examples:
phone_numberas primary key inusers(people change numbers).titleas primary key inposts(titles may change).
If you must allow changes, this can break foreign keys or require complex cascading updates.
Better approach:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
phone_number TEXT NOT NULL UNIQUE
);Reusing Primary Key Values
You should never "recycle" a primary key value for a different row.
Bad pattern:
- Delete row with
id = 10. - Insert a new and completely different row with
id = 10.
This confuses logs, references, and historical data. Let the database assign new IDs and keep them unique over time.
Overcomplicating Keys
Sometimes developers try to encode too much meaning into the primary key.
Example:
-- Trying to encode year and type into the key
order_id VARCHAR(50) PRIMARY KEY -- '2024-ONLINE-00001'This makes:
- The key longer.
- Indexes larger.
- Changes harder if the format must change.
A simpler approach:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
order_code TEXT NOT NULL UNIQUE -- like '2024-ONLINE-00001'
);Summary
- A primary key uniquely identifies each row in a table.
- Primary keys are unique, non‑NULL, and should be stable.
- You can use a single column or a composite of multiple columns as the primary key.
- Natural keys come from real world data. Surrogate keys are artificial, usually numeric IDs or UUIDs.
- In most backend applications, use a surrogate integer primary key (
id) and add UNIQUE constraints on important natural identifiers likeemail. - Creating a primary key automatically creates a unique index, which the database uses for fast lookups.
- Primary keys are central for relationships and foreign keys, so choosing them well is important for a healthy database design.
Views: 7
KAHIBARO