9.6. Relationships
Table of Contents
Understanding Relationships in Databases
When you build a backend that uses a relational database, you almost always work with data that is connected. Users own posts, orders contain items, students enroll in courses. These connections are called relationships.
This chapter focuses on how relationships work in relational databases and how to think about them when you design tables. Specific relationship types like one to one, one to many, and many to many have their own chapters, so here we focus on the shared ideas.
Why Relationships Matter
Without relationships, every table would be isolated. You would have to duplicate data everywhere.
Example without relationships:
orderstable stores customer name, address, and phone.paymentstable also stores customer name, address, and phone.shipmentstable again stores the same fields.
If a customer changes their address, you would need to update it in three places. If you forget one, your data becomes inconsistent.
With relationships, you can:
- Store the customer only once in a
customerstable. - Let
orders,payments, andshipmentsrefer to that customer. - Update the address in one row in
customers, and everything stays correct.
Relationships help you:
- Avoid duplication.
- Keep data consistent.
- Model the real world more clearly.
- Run powerful queries that join related data.
Core Concepts Behind Relationships
Keys and References
Relationships are built on two core ideas:
- Primary keys (covered in another chapter)
A primary key uniquely identifies a row in a table. For example: users(id, email, name, ...)idcould be the primary key.- Foreign keys (covered in its own chapter)
A foreign key stores the primary key of another table. It creates a link.
For example, if each order belongs to a user:
orders(id, user_id, total_amount, ...)user_idis a foreign key that points tousers.id.
The relationship is the logical connection between rows in different tables using these keys.
You can think about it like this:
- Primary key: "Who am I?"
- Foreign key: "Who do I belong to or refer to?"
Cardinality: How Many to How Many
Every relationship can be described by how many rows on each side can be linked. This is called cardinality.
There are three main patterns:
| Relationship type | Meaning (simplified) | Example |
|---|---|---|
| One to one | Each row connects to at most one row on the other side | User profile, passport |
| One to many | One row connects to many rows on the other side | User and posts, category and products |
| Many to many | Many rows connect to many rows on the other side | Students and courses, posts and tags |
You will see how to implement each one in the next chapters, but here is the key idea:
- The type of relationship describes reality, not SQL syntax.
- SQL syntax (foreign keys, join tables, constraints) is how you implement that reality.
Direction: Parent and Child
For a relationship between two tables, it often helps to think in terms of:
- Parent: The table that "owns" or "is referenced by" others.
- Child: The table that contains the foreign key pointing to the parent.
Example:
usersis the parent table.ordersis the child table.orders.user_idis a foreign key referencingusers.id.
You might hear:
- "A user has many orders."
- "An order belongs to a user."
Both describe the same relationship, just from different sides.
How Relationships Are Represented in SQL
Foreign Keys as Links
The most common way to represent a relationship in SQL is:
- One table has a primary key, for example
users.id. - Another table has a column with the same data type, for example
orders.user_id. - You declare that
orders.user_idis a foreign key referencingusers.id.
In SQL this often looks like:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
total NUMERIC(10, 2) NOT NULL,
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
);You now have a relationship:
- Each
orders.user_idmust match a validusers.id. - The database prevents you from inserting an order with a non-existent user, which protects data integrity.
Joins: Reading Related Data
Relationships let you query related data using JOINs (covered later in the SQL chapter). For now, just understand the idea.
Example: Get all orders with their user names.
SELECT
orders.id,
users.name,
orders.total
FROM orders
JOIN users ON orders.user_id = users.id;The relationship makes it possible to:
- Store users and orders in separate tables.
- Still see them together when you query.
Relationship Constraints and Integrity
Relationships are not just for convenience. They also help enforce referential integrity, which means:
- Every foreign key must point to a valid row.
- You cannot delete a parent row that still has child rows, unless you handle it explicitly.
Databases give you options for what happens when a parent row is updated or deleted:
| Option | Meaning |
|---|---|
ON DELETE RESTRICT | Prevent delete if child rows exist (default in many databases). |
ON DELETE CASCADE | Automatically delete child rows when the parent is deleted. |
ON DELETE SET NULL | Set the foreign key to NULL when the parent is deleted (if column allows NULL). |
ON UPDATE CASCADE | Update foreign keys when parent key changes (less common when using surrogate keys). |
Important rule: Always choose explicit ON DELETE behavior for foreign keys.
Never rely on "default" behavior without knowing what it is.
Example:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
total NUMERIC(10, 2) NOT NULL,
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE RESTRICT
);Here you cannot delete a user if they have orders. This can be useful when you must keep order history.
Common Relationship Scenarios
Here are some typical things you model in backends, and how relationships help.
Users and Content
- Case: A user can create many posts, comments, or reviews.
- Model:
userstable withid.poststable withuser_idforeign key.commentstable withuser_idforeign key.
Now you can:
- Find all posts for a user.
- Check if a user is allowed to modify a post by comparing user IDs.
- Delete a user and decide what happens to their content.
Catalogs and Categories
- Case: Products belong to categories, and each category can have many products.
- Model:
categoriestable withid.productstable withcategory_idforeign key.
You can:
- Show all products in a category.
- Show the category name next to each product.
- Prevent deleting a category that still has products, or cascade delete them if that makes sense.
Memberships, Tags, and Many Connections
- Case: Users can join many teams, and teams have many users. Posts can have many tags, and tags belong to many posts.
- Model:
- Two main tables, for example
usersandteams. - A third table to connect them, for example
user_teams(user_id, team_id).
This "connector" or "join" table holds pairs of foreign keys. Each row represents one link, for example "User 5 is in Team 3".
You will see the exact pattern of this in the many to many chapter, but it is helpful to already understand the idea.
Practical Design Tips for Relationships
Choose Clear Names
Use names that make it obvious what a foreign key points to.
Bad:
orders(uid)referencingusers.id.
Better:
orders(user_id)referencingusers.id.
Even better if there are multiple relationships to the same table:
orders(customer_id)referencingusers.id.orders/sales_rep_idreferencingusers.id.
One Direction in the Database, Both Directions in Code
In SQL, the relationship is stored in one direction:
ordershasuser_idthat points tousers.id.
But in application code you usually think in both directions:
- From user to orders: "get all orders for this user."
- From order to user: "get the user who owns this order."
Object relational mappers (ORMs) like SQLAlchemy will let you navigate both ways easily, even though the database only stores one direction.
Avoid Duplicating Relationship Information
If you already have a relationship, do not store the same information twice. That leads to confusion.
Example:
Tables:
departments(id, name)employees(id, department_id, name)
Do not add department_name to employees. You can always get it from departments using a join. If you copy it, it can get out of sync when names change.
Think About Deletion Rules
When you add a relationship, always ask:
- "What should happen if I delete the parent?"
Some common patterns:
| Relationship kind | Typical delete rule |
|---|---|
| Users and login sessions | Delete sessions when user is deleted (CASCADE). |
| Users and orders (history) | Do not allow deleting user if orders exist (RESTRICT). |
| Optional profile details | Set profile foreign key to NULL if details are deleted (SET NULL). |
Document your choices so that your team knows what to expect.
Examples of Relationship Design
Example 1: Simple Blog
You want to model:
- Users
- Posts
- Comments
Requirements:
- Each post belongs to one user (its author).
- Each comment belongs to one post and one user.
A possible schema:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
CONSTRAINT fk_posts_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
);
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
post_id INT NOT NULL,
user_id INT NOT NULL,
text TEXT NOT NULL,
CONSTRAINT fk_comments_post
FOREIGN KEY (post_id)
REFERENCES posts(id)
ON DELETE CASCADE,
CONSTRAINT fk_comments_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
);What you can do now:
- Get all posts by a user:
SELECT * FROM posts WHERE user_id = 123;- Get all comments for a post:
SELECT * FROM comments WHERE post_id = 456;- Get comments with author name:
SELECT comments.text, users.name
FROM comments
JOIN users ON comments.user_id = users.id
WHERE comments.post_id = 456;The relationships make these queries straightforward.
Example 2: Orders and Products
You want to model:
- Orders
- Products
- Each order can contain many products.
- Each product can appear in many orders.
This is a many to many relationship, so you use a link table:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id),
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id)
REFERENCES orders(id)
ON DELETE CASCADE,
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id)
REFERENCES products(id)
ON DELETE RESTRICT
);Relationships:
orderstoorder_itemstoproducts.- Each
order_itemsrow connects one order and one product, with a quantity.
Now you can:
- Find all products in an order.
- See how many times a product has been sold.
- Change a product price without touching
order_items.
How Relationships Impact Backend Code
Although this chapter focuses on databases, relationships strongly affect your backend code.
Relationships influence:
- API design
Do you expose/users/{user_id}/orders,/orders/{order_id}/items,/products/{product_id}/orders? - Validation
When you create an order, you must check that all referencedproduct_idvalues exist. - Authorization
To check if a user can edit a comment, you follow relationships: - Load the comment by
id. - Compare
comment.user_idto the current user ID. - Performance
Relationships often require joins. Poor design can lead to many joins or complex queries. Good design keeps relationships clear and efficient.
Key design rule:
Design relationships to reflect your real business rules.
Do not pick a relationship type just because it is easier to code.
Summary
- A relationship is a connection between rows in different tables, implemented with primary and foreign keys.
- The main relationship types are one to one, one to many, and many to many.
- Relationships are directionless in logic, but in the database one side usually holds a foreign key to the other.
- Relationships enable joins, protect data consistency, and shape how you design APIs and backend logic.
- Always think about naming, deletion rules, and avoiding duplicated relationship info.
In the next chapters you will look at each relationship type in detail and learn specific patterns to implement them in your schemas.
Views: 8
KAHIBARO