Definition
- Candidate key – Any minimal set of columns whose values are unique across all rows; a table can have several candidate keys.
- Natural key – A candidate key composed of real-world attributes such as
email,PAN, orISINthat exist independently of the database. - Surrogate key – An artificial, meaningless identifier generated purely to serve as the primary key, typically an
auto-incrementinteger or aUUID. - Primary key – The candidate key chosen as the official row identity for the table.
- Alternate key – Any candidate key that was not chosen as the primary key, enforced via a
UNIQUEconstraint. - Production preference – Surrogate keys are preferred because they are stable, narrow, performant, and do not leak business meaning or sensitive data.
At the National Bank of India, several things could identify Rajesh's account – his PAN, his registered email, or the internal account number SAV2001. Each is a candidate key. The PAN and email are natural keys because they are real-world facts. The internal account number, which the bank invented and which means nothing outside the bank, is a surrogate. The bank picks the surrogate as the official identity because Rajesh might change his email or the government might reissue PAN numbers, but SAV2001 never changes and reveals nothing private. PAN and email stay as alternate keys – still unique, just not the chosen identity.
Understanding the Concepts
What is a candidate key?
A candidate key is a minimal column or set of columns whose values are unique across all rows. Minimal means you cannot remove any column and still keep uniqueness. A table can have several candidate keys.
-- The accounts table has three candidate keys
CREATE TABLE accounts (
account_id VARCHAR(20) PRIMARY KEY, -- candidate 1 (surrogate)
pan VARCHAR(10) UNIQUE, -- candidate 2 (natural)
email VARCHAR(100) UNIQUE, -- candidate 3 (natural)
holder_name VARCHAR(100),
balance NUMERIC(15,2)
);
-- Each of account_id, pan, and email can uniquely identify a row
What is a natural key?
A natural key is a candidate key composed of attributes that have real-world meaning and exist independently of the database. Examples include email addresses, PAN numbers, passport numbers, and ISIN codes for securities. The key already has meaning in the business domain.
-- Using PAN as a natural primary key (common but problematic)
CREATE TABLE customers_natural (
pan VARCHAR(10) PRIMARY KEY, -- natural key: real-world meaning
full_name VARCHAR(100),
branch VARCHAR(50)
);
-- Every child table must reference PAN, spreading sensitive data
CREATE TABLE loans_natural (
loan_id SERIAL PRIMARY KEY,
pan VARCHAR(10) REFERENCES customers_natural(pan), -- PAN leaked!
amount NUMERIC(15,2),
status VARCHAR(20)
);
What is a surrogate key?
A surrogate key is a candidate key that the system invents purely to serve as an identifier. It carries no business meaning – typically an auto-incrementing integer or a UUID. The distinction from a natural key is meaning: a surrogate exists only to identify, while a natural key is also a fact about the entity.
-- Using a surrogate primary key (preferred in production)
CREATE TABLE customers_surrogate (
customer_id SERIAL PRIMARY KEY, -- surrogate: no meaning
pan VARCHAR(10) UNIQUE, -- alternate key (natural)
email VARCHAR(100) UNIQUE, -- alternate key (natural)
full_name VARCHAR(100),
branch VARCHAR(50)
);
-- Child tables reference the surrogate, not the PAN
CREATE TABLE loans_surrogate (
loan_id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers_surrogate(customer_id),
amount NUMERIC(15,2),
status VARCHAR(20)
);
What is an alternate key?
When you choose one candidate key as the primary key, every remaining candidate key becomes an alternate key. You still enforce it with a UNIQUE constraint so the database prevents duplicate values.
-- customer_id is primary, pan and email are alternate keys
ALTER TABLE customers_surrogate ADD CONSTRAINT uq_pan UNIQUE (pan);
ALTER TABLE customers_surrogate ADD CONSTRAINT uq_email UNIQUE (email);
Why do production schemas prefer a surrogate key?
Production schemas prefer surrogates for three reasons: stability, privacy, and performance.
| Reason | Natural Key | Surrogate Key |
|---|---|---|
| Stability | Changes (email, PAN reissue) | Never changes once assigned |
| Privacy | PAN/SSN spread to all child tables, URLs, logs | Reveals nothing sensitive |
| Performance | Wide strings, composite keys | Narrow integer, fast to index and join |
| Foreign keys | Cascading updates on key change | No cascading needed |
| Uniformity | Different width per table | Consistent integer type across all tables |
package com.bank.model;
import java.math.BigDecimal;
public class Account {
// Surrogate key: meaningless, stable, never exposed in URLs
private final long accountId;
// Natural keys kept for business logic but not as identity
private final String pan;
private final String email;
private final String holderName;
private BigDecimal balance;
public Account(long accountId, String pan, String email,
String holderName, BigDecimal balance) {
this.accountId = accountId;
this.pan = pan;
this.email = email;
this.holderName = holderName;
this.balance = balance;
}
public long getAccountId() { return accountId; }
public String getPan() { return pan; }
public String getEmail() { return email; }
}
What happens when a natural key changes?
When a natural key used as a primary key changes, every foreign key in every child table must be updated. This is cascading and error-prone.
-- Problem: PAN reissued by government, customer has loans, transactions, cards
-- With natural key as PK, all child tables must cascade
UPDATE customers_natural SET pan = 'NEWPN9876Z' WHERE pan = 'ABCDE1234F';
-- Must cascade to: loans, transactions, cards, audit_log, ...
-- Millions of rows updated across many tables
-- With surrogate key as PK, only ONE column in ONE row changes
UPDATE customers_surrogate SET pan = 'NEWPN9876Z' WHERE customer_id = 42;
-- No cascade needed. All child tables reference customer_id, not pan.
What are common mistakes?
The most common mistake is using a surrogate key without also adding a unique constraint on the natural key. This allows logical duplicates in the table.
-- WRONG: surrogate key but no unique constraint on natural key
CREATE TABLE customers_bad (
customer_id SERIAL PRIMARY KEY,
pan VARCHAR(10), -- no UNIQUE constraint!
full_name VARCHAR(100)
);
INSERT INTO customers_bad (pan, full_name) VALUES ('ABCDE1234F', 'Rajesh Kumar');
INSERT INTO customers_bad (pan, full_name) VALUES ('ABCDE1234F', 'Rajesh K.');
-- Both succeed! Two rows with the same PAN. Logical duplicate.
-- CORRECT: surrogate key PLUS unique constraint on natural key
CREATE TABLE customers_good (
customer_id SERIAL PRIMARY KEY,
pan VARCHAR(10) UNIQUE, -- prevents duplicates
full_name VARCHAR(100)
);
What about UUIDs versus auto-increment?
UUIDs are globally unique without coordination, making them ideal for distributed systems. However, random UUIDs hurt B-tree index locality because inserts scatter across the index. Time-ordered UUIDs (UUIDv7) solve this by being both globally unique and sequentially ordered.
-- Auto-increment: sequential, compact, good index locality
CREATE TABLE accounts_serial (
id SERIAL PRIMARY KEY,
holder_name VARCHAR(100)
);
-- UUID: globally unique, no coordination, but random scatter
CREATE TABLE accounts_uuid (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
holder_name VARCHAR(100)
);
-- UUIDv7 (time-ordered): best of both worlds
-- Requires PostgreSQL extension or application-generated values
What should a senior engineer know?
A senior engineer knows that surrogate keys provide stability and privacy, that natural keys must still be enforced as unique constraints, that UUIDs can hurt index locality unless time-ordered, and that the choice between integer and UUID depends on whether the system is distributed.
Visual Overview
┌───────────────────────────────────────────────────┐ │ Key Hierarchy for accounts Table │ ├───────────────────────────────────────────────────┤ │ │ │ Candidate Keys (all minimal unique identifiers) │ │ ┌─────────────┐ ┌───────┐ ┌─────────┐ │ │ │ account_id │ │ pan │ │ email │ │ │ │ (surrogate) │ │(nat.) │ │ (nat.) │ │ │ └──────┬──────┘ └───┬───┘ └────┬────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌───────────────────┐ │ │ │ PRIMARY KEY │ │ ALTERNATE KEYS │ │ │ │ (chosen) │ │ (UNIQUE constr.) │ │ │ └─────────────┘ └───────────────────┘ │ │ │ ├───────────────────────────────────────────────────┤ │ Why Surrogate Wins in Production │ ├───────────────────────────────────────────────────┤ │ │ │ Natural PK (PAN): │ │ customers ──PAN──→ loans │ │ ──PAN──→ transactions │ │ ──PAN──→ cards │ │ PAN changes → cascade ALL child tables │ │ │ │ Surrogate PK (customer_id): │ │ customers ──id──→ loans │ │ ──id──→ transactions │ │ ──id──→ cards │ │ PAN changes → update ONE column in ONE row │ │ │ └───────────────────────────────────────────────────┘
Key Takeaway
These four terms form a simple hierarchy. A candidate key is any minimal set of columns that uniquely identifies a row, and a table can have several. A natural key is a candidate key made of real-world attributes like an email or a PAN that exist independently of the database. A surrogate key is a candidate key the system invents purely to identify rows, carrying no business meaning – typically an auto-increment integer or a UUID. When you pick one candidate key to be the primary key, every other candidate key becomes an alternate key, which you keep as a unique constraint.
Production schemas usually make the primary key a surrogate for three reasons. First, stability: natural keys change – people change emails, governments reissue identifiers – and if the primary key changes, every foreign key in every child table must cascade, which is painful and error-prone. A surrogate assigned once never changes. Second, privacy: using a PAN or SSN as the primary key spreads that value into every child table, every URL, and every log line, creating compliance problems. A surrogate reveals nothing. Third, performance: a narrow integer key is small and fast to index and join, whereas a natural key may be a wide string or a composite that makes every index larger.
Crucially, you do not discard the natural key. You keep it as a unique constraint so the database still prevents duplicate PANs or emails. At National Bank of India, Pankaj originally designed the customers table with PAN as the primary key, and a single government PAN reissue forced a cascade across millions of child rows while the PAN leaked into logs. Rajesh redesigned with a surrogate customer_id as the primary key and PAN and email as alternate unique keys, so a PAN correction now touches exactly one column in one row.
Remember one sentence: Candidate keys are all the ways to identify a row, the natural one is real-world and the surrogate is invented, the chosen one is primary and the rest are alternate keys, and production prefers the surrogate because it never changes and leaks nothing.
---