← Назад к вопросам
Как сделать Many-to-Many связь в SQLAlchemy 2.0?
2.0 Middle🔥 121 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как сделать Many-to-Many связь в SQLAlchemy 2.0
Простой случай: базовая Many-to-Many
Когда нужны только связи без дополнительных данных:
from sqlalchemy import Column, Integer, String, Table, ForeignKey, create_engine
from sqlalchemy.orm import declarative_base, relationship, Session
Base = declarative_base()
# Таблица связи (association table)
student_course = Table(
'student_course',
Base.metadata,
Column('student_id', Integer, ForeignKey('students.id'), primary_key=True),
Column('course_id', Integer, ForeignKey('courses.id'), primary_key=True)
)
class Student(Base):
__tablename__ = 'students'
id = Column(Integer, primary_key=True)
name = Column(String)
# Связь many-to-many
courses = relationship(
'Course',
secondary=student_course,
back_populates='students'
)
class Course(Base):
__tablename__ = 'courses'
id = Column(Integer, primary_key=True)
name = Column(String)
students = relationship(
'Student',
secondary=student_course,
back_populates='courses'
)
# Использование
engine = create_engine('sqlite:///db.sqlite')
Base.metadata.create_all(engine)
with Session(engine) as session:
# Создаём объекты
student = Student(name='Alice')
course = Course(name='Python Basics')
# Связываем их
student.courses.append(course)
session.add(student)
session.commit()
# Получаем
alice = session.query(Student).filter_by(name='Alice').first()
print(alice.courses) # [<Course Python Basics>]
Сложный случай: Many-to-Many с дополнительными данными
Когда нужно хранить доп. информацию в таблице связи (например, дату добавления):
from datetime import datetime
# Вместо Table используем полноценный класс
class StudentCourse(Base):
__tablename__ = 'student_course'
student_id = Column(Integer, ForeignKey('students.id'), primary_key=True)
course_id = Column(Integer, ForeignKey('courses.id'), primary_key=True)
# Дополнительные поля в таблице связи
enrolled_at = Column(DateTime, default=datetime.utcnow)
grade = Column(String, nullable=True)
# Связи к родительским таблицам
student = relationship('Student', back_populates='course_registrations')
course = relationship('Course', back_populates='student_registrations')
class Student(Base):
__tablename__ = 'students'
id = Column(Integer, primary_key=True)
name = Column(String)
# Связь через association object
course_registrations = relationship(
'StudentCourse',
back_populates='student',
cascade='all, delete-orphan'
)
# Удобный доступ к курсам
@property
def courses(self):
return [reg.course for reg in self.course_registrations]
class Course(Base):
__tablename__ = 'courses'
id = Column(Integer, primary_key=True)
name = Column(String)
student_registrations = relationship(
'StudentCourse',
back_populates='course',
cascade='all, delete-orphan'
)
@property
def students(self):
return [reg.student for reg in self.student_registrations]
# Использование
with Session(engine) as session:
student = Student(name='Bob')
course = Course(name='Advanced Python')
# Создаём регистрацию с дополнительными данными
registration = StudentCourse(
student=student,
course=course,
grade='A'
)
session.add(registration)
session.commit()
# Получаем
bob = session.query(Student).filter_by(name='Bob').first()
for reg in bob.course_registrations:
print(f"{reg.course.name}: {reg.grade}")
SQLAlchemy 2.0 синтаксис (new style)
В SQLAlchemy 2.0 рекомендуется новый синтаксис:
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import List
from datetime import datetime
class Student(Base):
__tablename__ = 'students'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
# Many-to-many через association table
courses: Mapped[List['Course']] = relationship(
secondary=student_course,
back_populates='students'
)
class Course(Base):
__tablename__ = 'courses'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
students: Mapped[List['Student']] = relationship(
secondary=student_course,
back_populates='courses'
)
# Или с association object
class StudentCourse(Base):
__tablename__ = 'student_course'
student_id: Mapped[int] = mapped_column(
ForeignKey('students.id'),
primary_key=True
)
course_id: Mapped[int] = mapped_column(
ForeignKey('courses.id'),
primary_key=True
)
enrolled_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
grade: Mapped[str | None] = mapped_column(default=None)
# Relationships
student: Mapped['Student'] = relationship(back_populates='course_registrations')
course: Mapped['Course'] = relationship(back_populates='student_registrations')
class Student(Base):
__tablename__ = 'students'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
course_registrations: Mapped[List['StudentCourse']] = relationship(
back_populates='student',
cascade='all, delete-orphan'
)
class Course(Base):
__tablename__ = 'courses'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
student_registrations: Mapped[List['StudentCourse']] = relationship(
back_populates='course',
cascade='all, delete-orphan'
)
Запросы к Many-to-Many
from sqlalchemy import select
# SQLAlchemy 1.x стиль
session = Session(engine)
# Получить студента с его курсами
student = session.query(Student).filter_by(name='Alice').first()
print([c.name for c in student.courses])
# Найти все курсы студента
student = session.query(Student).options(
selectinload(Student.courses)
).filter_by(name='Alice').first()
# SQLAlchemy 2.0 синтаксис
stmt = select(Student).where(Student.name == 'Alice')
student = session.scalar(stmt)
# Найти студентов, которые учат Python
stmt = select(Student).join(
student_course
).join(Course).where(
Course.name.contains('Python')
)
students = session.scalars(stmt).unique().all()
Добавление и удаление связей
with Session(engine) as session:
student = session.query(Student).filter_by(id=1).first()
course = session.query(Course).filter_by(id=1).first()
# Добавить связь
student.courses.append(course)
session.commit()
# Удалить связь
student.courses.remove(course)
session.commit()
# Или с association object
student = session.query(Student).filter_by(id=1).first()
course = session.query(Course).filter_by(id=1).first()
reg = StudentCourse(student=student, course=course, grade='B')
session.add(reg)
session.commit()
# Удалить
session.delete(reg)
session.commit()
Best Practices
- Используй association object если нужны доп. данные
- Добавляй cascade для удаления - cascade='all, delete-orphan'
- Используй selectinload для загрузки связей
- Типизируй - Mapped[List['Course']]
- Тестируй запросы - смотри SQL через echo=True
Мany-to-Many это мощный инструмент, но нужно правильно его использовать!