Why the Relational Model?
E.F. Codd introduced the relational model in 1970 in his landmark paper A Relational Model of Data for Large Shared Data Banks. His central insight was that data should be described by its content — the values it holds — rather than by pointers or navigational paths. A relation is a set of tuples (rows), each conforming to a fixed set of attributes (columns), where every attribute draws its values from a named domain (data type). The word "table" is the everyday synonym for relation.
A relation has three defining properties:
- No duplicate rows. A relation is a mathematical set, so every row must be uniquely identifiable.
- No ordering of rows. The physical storage order carries no semantic meaning. Ordering is a query-time concern.
- Each cell holds one atomic value. You cannot store a list inside a single cell and still remain in first normal form.
These constraints are not bureaucratic rules; they are what makes querying predictable and optimization possible.
Keys
A candidate key is a minimal set of attributes whose values uniquely identify every row in a relation. "Minimal" means you cannot remove any attribute and still have uniqueness. From the candidate keys you choose one to be the primary key (PK) — the official row identifier. In PostgreSQL, declaring a column PRIMARY KEY creates a unique index and rejects NULLs automatically.
A foreign key (FK) is one or more columns in a "child" table whose values must match a primary key (or unique key) in a "parent" table — or be NULL. This referential integrity constraint is the mechanical enforcement of relationships between entities.
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL,
country_code CHAR(2) NOT NULL
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(customer_id),
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
total_usd NUMERIC(12, 2) NOT NULL CHECK (total_usd >= 0)
);The REFERENCES clause tells PostgreSQL: every customer<em>id stored in orders must exist in customers. Attempts to insert a row with a nonexistent customer will raise a foreign-key violation error immediately, before any row is committed.
Normalization
Normalization is the process of restructuring a schema to reduce data redundancy and the update anomalies that redundancy causes. The three most important normal forms are:
First Normal Form (1NF)
Every column holds atomic, indivisible values, and there are no repeating groups. A column storing comma-separated tags ("python,sql,ml") violates 1NF. Fix it by creating a separate item</em>tags table with one row per tag.
Second Normal Form (2NF)
A table is in 2NF if it is in 1NF and every non-key attribute is fully functionally dependent on the entire primary key. 2NF violations only arise when the PK is composite. If order<em>id, product</em>id is the PK of an order<em>lines table but product</em>name depends only on product<em>id, move product</em>name to a products table.
Third Normal Form (3NF)
A table is in 3NF if it is in 2NF and there are no transitive dependencies — no non-key column determines another non-key column. If zip<em>code determines city and state, storing all three in a customers table creates a transitive dependency. The fix is a zip</em>codes lookup table.
Why It Matters in Practice
In OLTP (Online Transaction Processing) systems, normalization prevents a single fact from being stored in multiple places, which means an UPDATE only needs to touch one row. In analytical (OLAP) workloads, schemas are often intentionally denormalized (star schema, wide tables) because read performance and query simplicity outweigh update cost. Understanding normalization lets you make that tradeoff deliberately rather than by accident.
A Working Sample Schema
The rest of this chapter uses these four tables. Run the DDL to follow along:
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
price_usd NUMERIC(10,2) NOT NULL,
in_stock BOOLEAN NOT NULL DEFAULT TRUE
);
INSERT INTO products (name, category, price_usd) VALUES
('Wireless Keyboard', 'Electronics', 49.99),
('USB-C Hub', 'Electronics', 29.99),
('Standing Desk', 'Furniture', 349.00),
('Ergonomic Chair', 'Furniture', 499.00),
('Notebook (A5)', 'Stationery', 4.99),
('Ballpoint Pens', 'Stationery', 7.49),
('Monitor Arm', 'Electronics', 89.95);