SQL4 min read6 sections

Primary key vs foreign key explained for beginners, with SQL examples

A primary key identifies 1 row. A foreign key points to a row in another table. Together they are how tables stay connected. This guide creates both, then breaks them on purpose so you can see the errors.

Solve “Distinct departments”

Free. Runs in your browser, no install.

Updated Written for: Beginners who have seen CREATE TABLE and want to understand keys and relationships.

What you will learn

  • A primary key uniquely identifies each row. It cannot repeat and cannot be NULL. A table has one.
  • A foreign key is a column that points to the primary key of another table, which links the rows.
  • The database refuses a foreign key value that does not exist, and refuses to delete a row that others still point to.
  • Some warehouses such as Snowflake and BigQuery accept keys but do not enforce them, so you must check data yourself.
On this page
  1. 1. Primary key: the unique label for a row
  2. 2. Foreign key: a pointer to another table
  3. 3. What the foreign key protects you from
  4. 4. Primary key vs foreign key side by side
  5. 5. Keys in warehouses and data engineering
  6. Quick checklist

Imagine a shop with a list of customers and a list of orders. Each order must say who placed it. You could write the customer's whole name and city on every order, but then Asha's details would be copied again and again. The better way is to give every customer a unique number and write only that number on each order. Those two ideas are exactly a primary key and a foreign key.

This guide builds the 2 tables, then tries to break them. The statements ran in DuckDB, and each error message is exactly what it printed. The ideas are the same in MySQL, PostgreSQL, SQL Server and Oracle.

Parent table
customers

customer_id is the primary key.

1 Asha, 2 Ravi.

Child table
orders

order_id is the primary key.

customer_id is the foreign key.

101 and 102 point to 1. 103 points to 2.

An order with customer_id 99 is refused, because there is no customer 99 to point to.

Figure 1. How an order finds its customer. The foreign key in orders holds a value that must exist as a primary key in customers.

Primary key: the unique label for a row

A primary key is a column (or a group of columns) whose value is different for every row, so it can point at exactly 1 row. It has 2 rules: it can never repeat, and it can never be empty (NULL). Here customer_id is the primary key of customers.

SQL
CREATE TABLE customers (
  customer_id INTEGER PRIMARY KEY,
  name        VARCHAR NOT NULL,
  city        VARCHAR
);

INSERT INTO customers VALUES
  (1, 'Asha', 'Pune'),
  (2, 'Ravi', 'Mumbai');

Try to add a second customer with the same id. The database refuses:

SQL
INSERT INTO customers VALUES (1, 'Meena', 'Chennai');
Constraint Error: Duplicate key "customer_id: 1" violates primary key constraint.

And try to add one with no id at all:

SQL
INSERT INTO customers VALUES (NULL, 'Meena', 'Chennai');
Constraint Error: NOT NULL constraint failed: customers.customer_id

Both are rejected, which is the point: you can trust that customer_id = 1 always means one person. A good primary key is stable (it never changes) and has no meaning in the real world. A phone number or email looks unique but people change them, so most tables use a plain number.

Foreign key: a pointer to another table

A foreign key is a column whose values must exist as primary keys in another table. In orders, the column customer_id is a foreign key pointing at customers.customer_id.

SQL
CREATE TABLE orders (
  order_id    INTEGER PRIMARY KEY,
  customer_id INTEGER REFERENCES customers (customer_id),
  amount      INTEGER
);

INSERT INTO orders VALUES
  (101, 1, 1200),
  (102, 1,  300),
  (103, 2,  800);
SQL
SELECT o.order_id, c.name, c.city, o.amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
ORDER BY o.order_id;
order_id | name | city   | amount
---------+------+--------+-------
101      | Asha | Pune   | 1200
102      | Asha | Pune   | 300
103      | Ravi | Mumbai | 800
(3 rows)

Each order now finds its customer by following the key. Asha has 2 orders: one customer can have many orders, but each order belongs to exactly one customer. That is called a one-to-many relationship, and it is the most common kind.

What the foreign key protects you from

First, an order for a customer who does not exist:

SQL
INSERT INTO orders VALUES (104, 99, 500);
Constraint Error: Violates foreign key constraint because key "customer_id: 99" does not exist in the referenced table

The database refuses, because there is no customer 99. Without the foreign key you would have an "orphan" order that points at nothing. Second, deleting a customer who still has orders:

SQL
DELETE FROM customers WHERE customer_id = 1;
Constraint Error: Violates foreign key constraint because key "customer_id: 1" is still referenced by a foreign key in a different table. If this is an unexpected constraint violation, please refer to our foreign key limitations in the documentation

Refused again, because orders 101 and 102 still point at Asha. You have to deal with her orders first, or tell the database what to do automatically with an ON DELETE rule. Finally, a foreign key column is allowed to be NULL unless you forbid it, which models an order with no known customer:

SQL
INSERT INTO orders VALUES (105, NULL, 500);
SQL
SELECT order_id, customer_id FROM orders ORDER BY order_id;
order_id | customer_id
---------+------------
101      | 1
102      | 1
103      | 2
105      | NULL
(4 rows)

The row with NULL is allowed. If every order must have a customer, declare the column NOT NULL too.

Primary key vs foreign key side by side

Primary keyForeign key
What it isThe unique label of a row in its own tableA column that points to another table's primary key
Can repeat?NoYes, many orders can point at one customer
Can be NULL?NoYes, unless you forbid it
How many per tableOne (it may use several columns)As many as you need
Protects againstDuplicate and unidentifiable rowsOrphan rows that point at nothing

Keys in warehouses and data engineering

In a normal application database (MySQL, PostgreSQL), keys are enforced as you saw. In many cloud warehouses they are not. Snowflake and BigQuery let you declare primary and foreign keys but do not stop you inserting duplicates or orphans, because checking keys on huge tables would slow loading down. The declaration still helps tools and humans understand the design, but the checking becomes your job: a data engineer writes a query that looks for duplicate keys and orphan values after each load, as shown in how to remove duplicate rows in SQL.

You will also meet two other words. A surrogate key is a number the system invents as the primary key. A natural key is a real-world value such as an email address used as the key. Data warehouses almost always add surrogate keys, because natural keys can change, which is explained in the SCD guide.

Quick checklist

  1. Every table gets a primary key. Make it a stable number.
  2. A column that refers to another table is a foreign key, and its values must exist in that table.
  3. Join on the key pair: orders.customer_id = customers.customer_id.
  4. Do not assume a warehouse enforces keys. Test for duplicates and orphans yourself.

Practise following keys with the free problems Employee department names and Customers who never ordered. To see why data is split into linked tables in the first place, read database normalization explained.

Keep practising

Take what you just read into the editor.

Browse all 36 guides