Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

So far, our database contained a single table (users); however
real applications almost always involve multiple tables that are related to each other !

Examples:

This is where relationships between tables come into play.


Why relationships matterยถ

Without relationships, all data would live in a single table, leading to:

Relational databases solve this by splitting data into multiple tables and linking them together using foreign keys.

A foreign key is simply a column that references the primary key of another table.


One-to-many relationshipsยถ

The most common relationship is one-to-many.

Example:
๐Ÿ‘‰ One User can have many Posts, but each Post belongs to exactly one User.

See also the complete code in python/db-relationships/user_posts.py.


Database viewยถ

Conceptually, this looks like:

The user_id column is what links a post to its author.


Defining a relationship in SQLModelยถ

With SQLModel, we explicitly describe both:

  1. the foreign key column, and

  2. the Python relationship.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sqlmodel import SQLModel, Field, Relationship

class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str

    posts: list["Post"] = Relationship(back_populates="user")


class Post(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str

    user_id: int = Field(foreign_key="user.id")
    user: User = Relationship(back_populates="posts")

Key ideas:

This allows you to navigate relationships naturally in Python:

post.user.name
user.posts

Many-to-one vs one-to-manyยถ

Note that:

In our example:

SQLModel (and SQLAlchemy) require you to define both sides explicitly if you want bidirectional access.


Many-to-many relationshipsยถ

Some relationships are many-to-many.

Example:
๐Ÿ‘‰ A student can attend many courses, and a course can have many students.

Relational databases handle this using an association table (also called a junction table).

See the code in python/db-relationships/students_courses.py for a complete example.


Association table conceptยถ

Instead of linking students directly to courses, we introduce a third table:

Each row in enrollments represents one association (one student in one course).


SQLModel approachยถ

In SQLModel, the association table is usually modeled explicitly:

1
2
3
class Enrollment(SQLModel, table=True):
    student_id: int = Field(foreign_key="student.id", primary_key=True)
    course_id: int = Field(foreign_key="course.id", primary_key=True)

Then referenced from both sides:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str

    courses: list["Course"] = Relationship(
        back_populates="students",
        link_model=Enrollment
    )


class Course(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str

    students: list[Student] = Relationship(
        back_populates="courses",
        link_model=Enrollment
    )

This may look verbose, but it gives you:


By default, relationships are lazy-loaded:

This is efficient, but it has consequences in APIs:

In practice, APIs often:


Relationships and API designยถ

Relationships strongly influence how you design your API:

There is no single correct answer โ€” it depends on:

A common rule of thumb:

Store relationships in the database,
but expose them explicitly and deliberately in the API.


Key takeawaysยถ

Understanding relationships is a major step toward building real-world data models โ€” and SQLModel makes this step far less painful than traditional ORMs.