C.W.K.
Stream
Lesson 03 of 10 · published

Primary Keys: Identity for Every Row

~12 min · schema, keys

Level 0Schema Seedling
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The row's permanent identity

A primary key is a row's permanent name — unique across the table, never null, ideally never changing. Without one, you cannot reliably point at a row from another table, from your application, or from a future you debugging at 2am. Every table should have one. No exceptions.

What makes a good primary key

  • Unique. No two rows share the value.
  • Not null. Every row has one.
  • Immutable. The value never changes after the row is created.
  • Compact and indexable. Integer or UUID — not a 200-character composite of business fields.

Surrogate vs natural keys

A natural key uses real-world data (email, ISBN, phone). A surrogate key is a system-generated value with no business meaning (auto-increment integer, UUID). Prefer surrogate keys; keep natural fields like email as UNIQUE constraints. Real-world identifiers do change (people change emails, ISBNs get reissued), and changing a primary key cascades pain through every foreign-key reference.

Composite primary keys

For pure junction tables (many-to-many bridges), a composite key of the two foreign keys is the natural fit and saves you a redundant surrogate column.

Code

Surrogate primary key (recommended default)·sql
CREATE TABLE users (
    id    INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    name  TEXT NOT NULL
);
Composite primary key for a junction table·sql
CREATE TABLE course_enrollments (
    student_id INTEGER NOT NULL REFERENCES students(id) ON DELETE CASCADE,
    course_id  INTEGER NOT NULL REFERENCES courses(id)  ON DELETE CASCADE,
    enrolled   DATE NOT NULL DEFAULT CURRENT_DATE,
    PRIMARY KEY (student_id, course_id)
);
UUID primary key for public-facing IDs·sql
CREATE TABLE api_tokens (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

External links

Exercise

For each table in any schema you've worked with, identify the primary key. Flag any that violate the rules (mutable fields used as PK, no PK at all, business-field composite keys). For one violator, sketch the schema change to fix it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.