SQLAlchemy: How to Use Many-To-Many Relationships

Updated: January 4, 2024 By: Guest Contributor Post a comment

Overview

Understanding many-to-many relationships in SQLAlchemy is crucial for building complex models in Python-based web applications.

Introduction to Many-To-Many

Many-to-many relationships occur when a single record in a table is associated with multiple records in another table, and vice versa. For instance, in a basic model of students and courses, a single student can enroll in many courses, and each course can be taken by multiple students. To represent this in a database, an association table is often used, creating a two-way link between the tables that represent each entity.

In SQLAlchemy, which is the Python SQL Toolkit and Object Relational Mapper (ORM), a many-to-many relationship is implemented using the relationship() function in combination with Table constructs to define the association table, and secondary keyword argument to define the association table that helps map the relations.

from sqlalchemy import Table, Column, Integer, ForeignKey, create_engine
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
engine = create_engine('sqlite:///:memory:')

association_table = Table('association', Base.metadata,
  Column('left_id', Integer, ForeignKey('left.id')),
  Column('right_id', Integer, ForeignKey('right.id'))
)

class Left(Base):
  __tablename__ = 'left'
  id = Column(Integer, primary_key=True)
  rights = relationship('Right', secondary=association_table)

class Right(Base):
  __tablename__ = 'right'
  id = Column(Integer, primary_key=True)

Defining the Models

To dive deeper into implementing many-to-many relationships with SQLAlchemy, let’s establish two entity models: Student and Course.

class Student(Base):
  __tablename__ = 'student'
  id = Column(Integer, primary_key=True)
  name = Column(String)

class Course(Base):
  __tablename__ = 'course'
  id = Column(Integer, primary_key=True)
  name = Column(String)
  students = relationship('Student', secondary=association_table)

Setting Up the Association Table

The association table resembles a standard table in SQLAlchemy but typically includes two foreign key columns that reference the primary keys of the tables that we are linking together. This helps to set up a many-to-many database schema.

students_courses_association = Table('students_courses_association', Base.metadata,
  Column('student_id', Integer, ForeignKey('student.id')),
  Column('course_id', Integer, ForeignKey('course.id'))
)

Working with the ORM

After defining your models and the association table, you can begin working with the ORM to add records and query the many-to-many relationship.

Inserting Records

To insert data into these tables, you must first create instances of your models, then establish the connections by appending instances to the relationship property.

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()

# Insert students
alice = Student(name='Alice')
bob = Student(name='Bob')
session.add_all([alice, bob])
session.commit()

# Insert courses
math = Course(name='Mathematics')
physics = Course(name='Physics')
session.add_all([math, physics])
session.commit()

Creating Associations

Associations can be created by adding instances of one model to the relationship property of instances of the other model.

alice.courses.append(math)
bob.courses.extend([math, physics])
session.commit()

Querying the Relationship

To query the relationships, you can use the ORM’s querying capabilities to load instances of one class that are related to instances of another.

for student in session.query(Student).join(Course, Student.courses).filter(Course.name == 'Mathematics'):
  print(student.name)

Conclusion

SQLAlchemy’s ORM allows for building complex database models with Python using idiomatical expressions that mirror the underlying SQL. By mastering many-to-many relationships, you are equipped to handle multifaceted data structures and create robust, data-driven applications using SQLAlchemy.