JavaRush /Курсы /Модуль 4: FastAPI /Связи между таблицами: OneToOne, ForeignKey, ManyToMany

Связи между таблицами: OneToOne, ForeignKey, ManyToMany

Модуль 4: FastAPI
11 уровень , 2 лекция
Открыта

В реальной жизни отношения между объектами бывают разные: "один человек — один паспорт", "один пользователь — много заказов", "много студентов — учатся у многих преподавателей". В реляционных базах данных это выражается через отношения:

  • OneToOne (Один-к-одному): каждая строка в одной таблице соответствует только одной строке в другой. Аналог — один человек и его паспорт.
  • ForeignKey (Один-ко-многим): одна строка в таблице A может быть связана с множеством строк в таблице B. Например, один пользователь и его заказы.
  • ManyToMany (Многие-ко-многим): множество строк в одной таблице может быть связано с множеством строк в другой. Например, студенты посещают множество курсов, а курсы имеют множество студентов.

Применение связей

Связи позволяют объединять таблицы, обеспечивая согласованность данных. Представьте хаос, если вы хранили бы информацию обо всем в одной таблице. Связи упрощают архитектуру.


Реализация связей с использованием SQLAlchemy

В SQLAlchemy связь "один-к-одному" создаётся с помощью relationship и ForeignKey. Рассмотрим пример:


from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

# Таблица User
class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    
    # Связь с таблицей Profile
    profile = relationship("Profile", back_populates="user", uselist=False)

# Таблица Profile
class Profile(Base):
    __tablename__ = 'profiles'
    
    id = Column(Integer, primary_key=True)
    bio = Column(String)
    user_id = Column(Integer, ForeignKey('users.id'))
    
    # Связь с таблицей User
    user = relationship("User", back_populates="profile")

В этом коде:

  • У пользователя может быть только один профиль, и наоборот.
  • Используем uselist=False, чтобы указать, что связь "один-к-одному".
  • back_populates позволяет двум связанным таблицам "знать" друг о друге.

Для реализации "один-ко-многим" мы используем ForeignKey и relationship. Пример:


class Author(Base):
    __tablename__ = 'authors'
    
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    
    # Связь с книгами
    books = relationship("Book", back_populates="author")

class Book(Base):
    __tablename__ = 'books'
    
    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    author_id = Column(Integer, ForeignKey('authors.id'))
    
    # Связь с автором
    author = relationship("Author", back_populates="books")

Объяснение:

  • У одного автора может быть много книг (например, Дж. Роулинг написала больше одной книги).
  • ForeignKey в таблице Book создаёт внешний ключ на таблицу Author.

Отношения "многие-ко-многим" хитрее, так как требуют промежуточную таблицу. Вот как это делается:


from sqlalchemy import Table

# Вспомогательная таблица
association_table = Table(
    'student_course', Base.metadata,
    Column('student_id', Integer, ForeignKey('students.id')),
    Column('course_id', Integer, ForeignKey('courses.id'))
)

# Таблица Student
class Student(Base):
    __tablename__ = 'students'
    
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    
    # Связь с курсами
    courses = relationship("Course", secondary=association_table, back_populates="students")

# Таблица Course
class Course(Base):
    __tablename__ = 'courses'
    
    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    
    # Связь со студентами
    students = relationship("Student", secondary=association_table, back_populates="courses")

Объяснение:

  • Вспомогательная таблица association_table хранит связи между студентами и курсами.
  • Параметр secondary указывает на эту таблицу.

Практические применения связей

Допустим, вы строите базу данных для e-commerce. У вас могут быть такие связи:

  • Пользователь (User) имеет заказы (Orders) — "один-ко-многим".
  • Заказ (Order) содержит товары (Products) — "многие-ко-многим".
  • Профиль (Profile) привязан к пользователю — "один-к-одному".

Используя SQLAlchemy, можно выразить это так:


class Order(Base):
    __tablename__ = 'orders'
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    
    user = relationship("User", back_populates="orders")
    products = relationship("Product", secondary='order_product', back_populates="orders")


class Product(Base):
    __tablename__ = 'products'
    
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    price = Column(Integer)
    
    orders = relationship("Order", secondary='order_product', back_populates="products")


order_product_table = Table(
    'order_product', Base.metadata,
    Column('order_id', Integer, ForeignKey('orders.id')),
    Column('product_id', Integer, ForeignKey('products.id'))
)

Полезные советы

  • Используйте связи экономно. Не всегда стоит делать таблицы слишком связанными.
  • Если данные одной таблицы редко используются, рассмотрите возможность денормализации.
  • Для работы с большим объемом ManyToMany данных используйте индексы.
  • Частая ошибка, которой стоит избегать — забыть указать back_populates или ForeignKey. Это приведёт к отсутствию связи между таблицами или ошибкам при выполнении запросов.

Для тех, кто хочет копнуть глубже, документация SQLAlchemy всегда готова прийти на помощь!

1
Задача
Модуль 4: FastAPI, 11 уровень, 2 лекция
Недоступна
Создание связи OneToOne
Создание связи OneToOne
1
Задача
Модуль 4: FastAPI, 11 уровень, 2 лекция
Недоступна
ManyToMany через вспомогательную таблицу
ManyToMany через вспомогательную таблицу
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ