У реальному світі сутності рідко існують в ізоляції, і наша база даних — не виняток. У реляційній моделі бази даних зовнішні ключі (ForeignKey) дозволяють пов'язувати записи між таблицями, створюючи відносини, такі як "один до одного", "один до багатьох", "багато до багатьох". Це як родинне дерево, родина, тільки без сварок за вечерею.
Наприклад:
- У нас є таблиця
Users, що містить інформацію про користувачів. - І таблиця
Postsз записами блогу, які ці користувачі написали. - Кожен запис у
Postsпосилається на певного користувача з таблиціUsers.
Типи відносин
1. Один до багатьох (One-to-Many). Кожен користувач може мати кілька постів, але кожен пост пов'язаний лише з одним користувачем.
2. Один до одного (One-to-One). Кожному користувачу відповідає лише один профіль у базі (наприклад, таблиця Profiles).
3. Багато до багатьох (Many-to-Many). Кожен користувач може підписатися на багатьох авторів, а кожен автор може мати багатьох підписників. Наприклад, таблиця Followers.
Приклади використання зв'язків
Давайте створимо базові моделі для користувачів і їхніх постів:
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, index=True) # Первинний ключ
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
# Зв'язок з таблицею Posts
posts = relationship("Post", back_populates="owner")
# Модель Post
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
content = Column(String)
user_id = Column(Integer, ForeignKey('users.id')) # Зовнішній ключ
# Зв'язок з користувачем
owner = relationship("User", back_populates="posts")
ForeignKeyвPost
Полеuser_idв таблиціpostsпосилається на колонкуidв таблиціusers.relationship
В моделіUserіPostвикористовуєтьсяrelationship, щоб SQLAlchemy міг зрозуміти зв'язок між таблицями.
back_populatesстворює двосторонній зв'язок: це дозволяє переходити від користувача до його постів і назад.
- Живий зв'язок
Тепер, якщо ми витягнемо об'єктUserз бази даних, ми можемо звернутися до його постів черезuser.posts. Зручно? Ще б!
Тепер створимо користувача і пару постів, пов'язаних з ним:
from sqlalchemy.orm import Session
from models import User, Post
# Функція для додавання даних
def create_user_with_posts(db: Session):
# Створюємо користувача
new_user = User(name="Аліса", email="alice@example.com")
db.add(new_user)
db.commit()
db.refresh(new_user)
# Створюємо пости, пов'язані з цим користувачем
post1 = Post(title="Перший пост", content="Привіт, світ!", owner=new_user)
post2 = Post(title="Ще один пост", content="Вчуся SQLAlchemy!", owner=new_user)
db.add_all([post1, post2])
db.commit()
# Використання функції
create_user_with_posts(db_session)
Тепер ми можемо отримати всі записи користувача "Аліса" (і її пости):
# Запит користувача
user = db.query(User).filter(User.name == "Аліса").first()
# Доступ до постів
print(user.posts)
# Вивід постів
for post in user.posts:
print(f"Заголовок: {post.title}, Текст: {post.content}")
Запити з використанням зв'язків
Якщо хочеш виконати запит, який напряму зв'язує таблиці (наприклад, щоб повернути пости разом із іменем автора), SQLAlchemy дозволяє це зробити через join.
from sqlalchemy.orm import joinedload
# Запит всіх постів з даними користувача
posts = db.query(Post).options(joinedload(Post.owner)).all()
for post in posts:
print(f"Заголовок: {post.title}, Автор: {post.owner.name}")
Робота з "багато до багатьох"
Іноді одного зв'язку замало. Наприклад, у системі підписок користувачі можуть підписуватися один на одного. Для цього створюється проміжна таблиця. Створімо її:
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
# Проміжна таблиця
followers_association = Table(
'followers',
Base.metadata,
Column('follower_id', Integer, ForeignKey('users.id')),
Column('followed_id', Integer, ForeignKey('users.id'))
)
# Модель User з відношенням багато до багатьох
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
# Зв'язок підписників
followers = relationship(
"User",
secondary=followers_association,
primaryjoin=id == followers_association.c.followed_id,
secondaryjoin=id == followers_association.c.follower_id,
backref="followed_by"
)
Тепер додамо користувача і його підписників:
# Додавання підписників
def add_followers(db: Session):
user1 = User(name="Боб")
user2 = User(name="Чарлі")
user3 = User(name="Дейв")
user1.followers.append(user2) # Чарлі підписаний на Боба
user1.followers.append(user3) # Дейв підписаний на Боба
db.add_all([user1, user2, user3])
db.commit()
Помилки та особливості роботи
- Каскадне видалення
Якщо ви намагаєтеся видалити запис, який пов'язаний з іншою таблицею, може виникнути помилка зовнішнього ключа. У таких випадках рекомендується використовувати каскадне видалення (cascade="all, delete"). - Спроба оновити зовнішній ключ напряму
Пам'ятайте, що зручніше працювати зrelationship, ніж напряму оновлювати полеForeignKey. - Проміжна таблиця
Якщо в проміжній таблиці забути вказати зовнішній ключ, SQLAlchemy не буде знати про зв'язок, і вся магія зникне.
Практичне застосування
Робота з реляційними моделями і зв'язками — це хліб і масло будь-якого веб-застосунку, який працює з базами даних. Бо ізольовані дані на практиці зустрічаються вкрай рідко. Такі навички затребувані на співбесідах, коли потрібно показати, що ви вмієте моделювати реальні відносини між сутностями.
Для реальних проектів:
- Можна будувати складні системи авторизації, де користувач пов'язаний з ролями та дозволами.
- Або створювати табличні структури для електронної комерції, де замовлення пов'язані з користувачами та товарами.
Робота з реляційними моделями в SQLAlchemy дозволяє не тільки спрощено взаємодіяти з базою даних, але й зберігати можливість оптимізації та керування складними реляційними структурами. З цим інструментом ваші бази даних працюватимуть не тільки правильно, але й красиво.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ