Relational database concepts
Keys and relationships
The idea in 30 seconds
A primary key gives every row an identity: unique and never NULL. A foreign key is a column that must match a primary key in another table, which is how tables connect without copying data. With those two tools you can express one-to-many and many-to-many relationships, and the database refuses data that would break them.
Helps to know first: What data is, and why we need a database
On this page
Primary keys: an identity for every row
A primary key is a column, or a group of columns, whose value is different for every row and never NULL. In customers it is id:
CREATE TABLE customers (
id integer PRIMARY KEY,
name text NOT NULL,
email text NOT NULL UNIQUE,
city text,
joined_on date NOT NULL
);
Declaring it does two things. The database refuses any row that would repeat an id or leave it empty, and it builds a B-tree index on the column, so finding a customer by id is the few-page lookup from B-tree indexes.
email is marked UNIQUE too. A table can have only one primary key but any number of unique columns. Try to add a second customer with Asha’s email:
ERROR: duplicate key value violates unique constraint "customers_email_key"
DETAIL: Key (email)=(asha@example.com) already exists.
Foreign keys: connecting tables
An order belongs to a customer. Rather than copying the customer’s name and email into every order, orders stores the customer’s id and declares that it must match a real customer:
CREATE TABLE orders (
id integer PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers (id),
status text NOT NULL,
ordered_at timestamptz NOT NULL
);
customer_id is a foreign key. The database now guarantees every order points at a customer that exists. Try an order for customer 99:
ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey"
DETAIL: Key (customer_id)=(99) is not present in table "customers".
This guarantee is called referential integrity: no reference points at nothing. With files you would have had to check it yourself, in every program, every time.
The three shapes of relationship
| Relationship | Example in shop | How it is stored |
|---|---|---|
| One-to-many | One customer has many orders | A foreign key on the “many” side: orders.customer_id |
| Many-to-many | An order has many products; a product appears in many orders | A third table with two foreign keys: order_items (order_id, product_id) |
| One-to-one | A customer has at most one loyalty card | A foreign key that is also unique |
order_items is worth a closer look. Each row says “this order contains this product, in this quantity, at this price”:
CREATE TABLE order_items (
order_id integer NOT NULL REFERENCES orders (id),
product_id integer NOT NULL REFERENCES products (id),
quantity integer NOT NULL,
unit_price numeric(10, 2) NOT NULL,
PRIMARY KEY (order_id, product_id)
);
Its primary key is two columns together: the same product can appear in many orders, and an order can contain many products, but the same product appears only once per order. It also stores unit_price, because the price a customer paid must not change when the catalogue price changes later. In shop, order 103 bought a mouse for 1,100 while the catalogue says 1,200.
Check yourself
What two things does the database enforce on a primary key column, and what does it build for it?
Show answer
Every value must be unique and not NULL. It also builds a unique B-tree index on the key so rows can be found by it quickly.
How do you store “a product can be in many orders and an order can contain many products”?
Show answer
With a third table, here
order_items, holding one row per order-product pair, with a foreign key to each side. Its primary key is the pair(order_id, product_id).Why does
order_itemsstoreunit_pricewhenproductsalready has a price?Show answer
The catalogue price changes over time. The order must remember what the customer actually paid on the day, so that price is part of the order item, not looked up later.
Finished reading?
Saved in this browser only.
Sources
Where each claim on this page comes from.
- 1.PostgreSQL documentation: 5.5 ConstraintsPrimary· §5.5.4 Primary Keys
A primary key requires values to be unique and not null; it automatically creates a unique B-tree index; a table has at most one primary key; primary keys can span several columns.
- 2.PostgreSQL documentation: 5.5 ConstraintsPrimary· §5.5.5 Foreign Keys
A foreign key requires values to match a row in another table, maintaining referential integrity.
- 3.PostgreSQL documentation: 2.2 ConceptsPrimary· §2.2 Concepts
Each table is a named collection of rows with the same named, typed columns.
Go further
What to read next, and why it is worth the time.
- 1.PostgreSQL documentation: 5.5 ConstraintsPrimary· §5.5.4–5.5.5
Both sections are short and full of examples, including what happens to orders when a product is deleted.
Applies to PostgreSQL 18. Draft: not yet fact-checked end to end.