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 завжди готова прийти на допомогу!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ