В реальном мире сущности редко существуют в изоляции, и наша база данных — не исключение. В реляционной модели базы данных внешние ключи (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 позволяет не только упрощённо взаимодействовать с базой данных, но также сохраняет возможность оптимизации и управления сложными реляционными структурами. С этим инструментом ваши базы данных будут работать не только правильно, но и красиво.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ