By WebNest Studio Engineering Team
Database Design Tutorial
Modeling Relationships Between Entities
Once entities are identified, the next question is how they connect to each other. A relationship captures a real-world association — a customer places orders, an employee manages a department, a product belongs to a category — and in a relational database, every relationship must ultimately be expressed through foreign keys or, for many-to-many associations, through an entire join table.
Relationships are not optional decoration on top of entities; they are where most of the actual design difficulty lives. Getting entities right is usually straightforward once you know the domain, but deciding how those entities reference each other — including what happens when a referenced row is deleted, and whether a relationship is optional or mandatory — is where schemas succeed or quietly accumulate bad data.
Why it matters
A foreign key says how two tables are connected, and its delete rule says what happens to the children when the parent goes. Getting that rule wrong either blocks legitimate deletions or silently destroys data that should have been kept. It is a decision to make deliberately for every relationship.
From Sentence to Foreign Key
A relationship in a requirement sentence usually has a direction and an owner. "Each order belongs to one customer" tells you the foreign key lives on the orders table (orders.customer_id references customers.id) — the "many" side of a one-to-many relationship always holds the foreign key pointing to the "one" side. Getting the direction backwards (putting a customer_id-like column on the wrong table) is a common early mistake that becomes obvious only once you try to model a customer with more than one order.
Mandatory vs. Optional Relationships
A relationship is mandatory if a row cannot exist without it — an order_items row makes no sense without an order, so order_id should be NOT NULL. A relationship is optional if the association can be absent — a customer may or may not have an assigned account manager, so account_manager_id can allow NULL. Deciding this explicitly, column by column, prevents both false NOT NULL constraints that block legitimate data entry and false nullable columns that let genuinely required links go missing.
Referential Actions: What Happens on Delete or Update
Every foreign key needs an explicit policy for what happens when the referenced row is deleted or its key changes: ON DELETE CASCADE (delete dependents automatically), ON DELETE RESTRICT (block the delete if dependents exist), or ON DELETE SET NULL (clear the reference but keep the dependent row). Choosing this deliberately — rather than accepting a database default you haven't checked — is part of relationship design, not an afterthought: deleting a customer should probably not silently cascade-delete years of order history.
- CASCADE — appropriate for true ownership, e.g. deleting an order should delete its order_items.
- RESTRICT / NO ACTION — appropriate when the dependent data must be preserved or archived first, e.g. blocking deletion of a product that appears in past orders.
- SET NULL — appropriate for optional associations, e.g. clearing assigned_employee_id if that employee record is removed.
Example
A one-to-many relationship with an explicit, deliberate referential action
CREATE TABLE departments (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE employees (
id BIGINT PRIMARY KEY,
full_name VARCHAR(150) NOT NULL,
department_id BIGINT NULL REFERENCES departments(id) ON DELETE SET NULL,
manager_id BIGINT NULL REFERENCES employees(id) ON DELETE SET NULL
);
employees.department_id is a mandatory-looking but actually optional
relationship: an employee can be unassigned to any department. If a
department is deleted, ON DELETE SET NULL clears department_id on its
employees rather than deleting the employee rows or blocking the delete.
manager_id is a self-referencing relationship (an employee can manage
other employees) with the same "orphan gracefully" policy.
Common Mistakes
- Putting the foreign key on the "one" side of a one-to-many relationship instead of the "many" side, making it impossible for one parent to have multiple children.
- Leaving every foreign key nullable "just in case," instead of deciding per relationship whether the association is truly mandatory or optional.
- Accepting the database's default referential action without thinking about it, resulting in either accidental cascading deletes of important history or blocked deletes that surprise the application.
- Modeling a relationship only in application code (fetching related rows manually by matching values) instead of declaring it as a real foreign key constraint the database can enforce.
Exercise
Order items cannot exist without their order. Create order_items so that deleting an order automatically deletes its items. The data is inserted and order 1 is then deleted. Count the items that remain as remaining.
Expected output: remaining then 0
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT NOT NULL);
-- TODO: order_items (id, order_id, product) whose rows are deleted with their order
INSERT INTO orders (id, customer) VALUES (1, 'Asha');
INSERT INTO order_items (id, order_id, product) VALUES (1, 1, 'pen'), (2, 1, 'book');
DELETE FROM orders WHERE id = 1;
SELECT COUNT(*) AS remaining FROM order_items;
Show hints
- The referential action is written after the reference:
REFERENCES orders(id) ON DELETE CASCADE. - Without it, the delete would be refused, because the items still refer to the order.
Show solution
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT NOT NULL);
CREATE TABLE order_items (
id INTEGER PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product TEXT NOT NULL
);
INSERT INTO orders (id, customer) VALUES (1, 'Asha');
INSERT INTO order_items (id, order_id, product) VALUES (1, 1, 'pen'), (2, 1, 'book');
DELETE FROM orders WHERE id = 1;
SELECT COUNT(*) AS remaining FROM order_items;
Quick quiz
1. What does ON DELETE CASCADE do?
- A. Blocks the deletion of the parent row
- B. Sets the foreign key to NULL
- C. Deletes the child rows when the parent row is deleted
- D. Deletes the parent when a child is deleted
Show answer
C. It suits children that have no meaning without the parent.
2. How do you make a relationship mandatory, so that every order must have a customer?
- A. Add an index
- B. Declare the foreign key column NOT NULL
- C. Use ON DELETE SET NULL
- D. Make the column UNIQUE
Show answer
B. A nullable foreign key makes the relationship optional.
3. Which delete rule should protect a customer who has orders from being removed by accident?
- A. ON DELETE RESTRICT (or NO ACTION)
- B. ON DELETE CASCADE
- C. ON DELETE SET DEFAULT
- D. No foreign key at all
Show answer
A. The deletion is refused while child rows exist.
Interview questions
What referential actions are available on a foreign key?
Show answer
NO ACTION and RESTRICT refuse to delete or update a parent row that still has children; this is the default. CASCADE applies the same deletion or key change to the children. SET NULL sets the foreign key in the children to null, keeping them without a parent. SET DEFAULT sets it to the column's default value. Each can be specified separately for ON DELETE and ON UPDATE.
When is ON DELETE CASCADE dangerous?
Show answer
When the children have value independent of the parent. Cascading from customers to orders would erase financial records when a customer is deleted, and a chain of cascades can remove far more than intended from a single statement. It is appropriate for true parts of the parent, such as order lines. For records that must be kept, use RESTRICT, or mark the parent as deleted without removing the row.
Key Points to Remember
- The "many" side of a one-to-many relationship holds the foreign key that points to the "one" side.
- Every relationship should be explicitly classified as mandatory (NOT NULL foreign key) or optional (nullable foreign key).
- Referential actions (CASCADE, RESTRICT, SET NULL) must be chosen deliberately based on whether the dependent data represents true ownership or a loose association.
- Relationships belong in the database as enforced foreign key constraints, not only as an implicit convention in application code.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs your SQL on SQLite in your browser. Standard SQL works; a few features that exist only in PostgreSQL or MySQL do not. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering