Нужно ли оставлять колонку order_id у item при использовании relationship в SQLAlchemy
Что проверяют
Понимаете ли вы, что relationship() живёт в Python, а связь между таблицами держит внешний ключ в базе. Это вопрос на границу между ORM и схемой, и попутно на типовые грабли: N+1 и каскады.
Как отвечать
Да, колонку оставляем. relationship() ничего не создаёт в схеме, он только объясняет ORM, как ходить по уже существующему ForeignKey. Убрать order_id и оставить relationship нельзя: собирать order.items будет не из чего.
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
order_id: Mapped[int] = mapped_column(ForeignKey("order.id"))
order: Mapped["Order"] = relationship(back_populates="items")Что добавить:
order_idиorderне дублируют друг друга: первое это колонка, второе навигация по ней, присвоить можно любое из двух;back_populatesдержит обе стороны согласованными в рамках сессии;- если товар живёт сам по себе и попадает в разные заказы, связь становится многие ко многим, и внешний ключ переезжает в таблицу позиций заказа вместе с количеством и ценой на момент покупки;
- по внешнему ключу нужен индекс: сама PostgreSQL его не создаёт, а по этой колонке идёт и выборка позиций, и проверка при удалении заказа.
Где спотыкаются
Называют внешний ключ избыточностью и предлагают его удалить. Нормализация запрещает дублировать факты, а внешний ключ не дубль: это единственное место, где связь вообще записана.
Ловят N+1. Цикл по заказам с обращением к order.items порождает отдельный запрос на каждый заказ, и на сотне заказов страница встаёт. Лечится selectinload или joinedload, а не кэшем поверх.
Путают каскады. ondelete="CASCADE" работает в базе, cascade="all, delete-orphan" в сессии ORM, и это разные механизмы: удаление через session.delete может не сработать так, как удаление запросом. Если колонка при этом nullable, после удаления заказа остаются строки с пустой ссылкой.