One-to-many and many-to-many

Database design · Essentials · 10 min

Getting cardinality right is the most common modelling decision and the hardest to undo once data exists.

Every relationship has a cardinality: how many rows on each side can be linked.

  • One-to-many (1:N): a customer has many orders, each order has one customer. Put a foreign key on the many side: orders.customer_id. This is by far the most common shape.
  • Many-to-many (M:N): a student takes many courses and a course has many students. Relational tables can't hold a list in a column, so you add a junction table (also called an associative or join table) with a foreign key to each side. Its primary key is usually the pair, which also prevents duplicates.
  • One-to-one (1:1): a user has at most one profile. Use a foreign key that is also UNIQUE (or make the child's primary key the foreign key). Often 1:1 tables are simply merged, unless one side is optional, large or has different access rules.

Junction tables frequently grow attributes of their own: an enrollment has a grade and an enrolled date. When that happens the relationship has become an entity, which is fine — give it a name from the domain.

Optionality matters too. NOT NULL on the foreign key says "every order must have a customer"; a nullable one says the link is optional. Decide deliberately, because the constraint is what stops orphaned or half-linked data.

Example

CREATE TABLE student (id int PRIMARY KEY, name text NOT NULL);
CREATE TABLE course  (id int PRIMARY KEY, title text NOT NULL);

-- M:N via a junction table that carries its own attributes
CREATE TABLE enrollment (
    student_id  int NOT NULL REFERENCES student(id) ON DELETE CASCADE,
    course_id   int NOT NULL REFERENCES course(id),
    enrolled_on date NOT NULL DEFAULT current_date,
    grade       text,
    PRIMARY KEY (student_id, course_id)       -- no duplicate enrollments
);

-- 1:1 optional: at most one profile per student
CREATE TABLE profile (
    student_id int PRIMARY KEY REFERENCES student(id),
    bio        text
);

Remember

  • 1:N: foreign key on the many side
  • M:N: junction table with a composite primary key
  • 1:1: a foreign key that is also unique (or the primary key)

Watch out for

  • Storing comma-separated ids in a column instead of a junction table.

Check yourself

  1. Students take many courses and courses have many students. How is this modelled?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-FK