Я борюсь с реализацией концепции «цитирования научной статьи» в SQL.
У меня есть таблица Papers. Каждый Paper может цитировать множество других Paper и, наоборот, его могут цитировать многие другие.
Вот код, который я написал
class Paper(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
bibliography: List["Citation"] = Relationship(back_populates = "citing")
cited_by: List["Citation"] = Relationship(back_populates = "cited")
class Citation(SQLModel, table=True):
citing_id: Optional[int] = Field(default=None, primary_key=True, foreign_key = "paper.id")
citing: "Paper" = Relationship(back_populates = "bibliography")
cited_id: Optional[int] = Field(default=None, primary_key=True, foreign_key = "paper.id")
cited: "Paper" = Relationship(back_populates = "cited_by")
Это не работает:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Paper.bibliography - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Проблема в том, что я дважды написал foreign_key = "paper.id", но не знаю, как это исправить.
Чтобы воспроизвести ошибку:
sqlmodel
.from typing import List
from typing import Optional
from sqlmodel import create_engine
from sqlmodel import Field
from sqlmodel import Relationship
from sqlmodel import Session
from sqlmodel import SQLModel
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
# class Paper(SQLModel, table=True): ...
# class Citation(SQLModel, table=True): ...
if __name__ == "__main__":
SQLModel.metadata.create_all(engine)
Paper()
Я использую SQLModel , но ответ в SQLAlchemy тоже подойдет.
Обработка нескольких возможных JOIN условий в SQLAlchemy задокументирована здесь . Решение состоит в том, чтобы явно передать аргумент Foreign_keys вашему конструктору RelationshipProperty.
В этом случае вам нужно будет указать это для всех четырех рассматриваемых отношений.
Поскольку SQLModel в настоящее время не позволяет напрямую передавать все доступные аргументы отношений в свой конструктор (хотя я работаю над PR для этого), вам необходимо использовать параметр sa_relationship_kwargs.
Вот рабочий пример:
from typing import Optional
from sqlmodel import Field, Relationship, SQLModel
class Paper(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
bibliography: list["Citation"] = Relationship(
back_populates = "citing",
sa_relationship_kwargs = {"foreign_keys": "Citation.citing_id"},
)
cited_by: list["Citation"] = Relationship(
back_populates = "cited",
sa_relationship_kwargs = {"foreign_keys": "Citation.cited_id"},
)
class Citation(SQLModel, table=True):
citing_id: Optional[int] = Field(
default=None,
primary_key=True,
foreign_key = "paper.id",
)
citing: Paper = Relationship(
back_populates = "bibliography",
sa_relationship_kwargs = {"foreign_keys": "Citation.citing_id"},
)
cited_id: Optional[int] = Field(
default=None,
primary_key=True,
foreign_key = "paper.id",
)
cited: Paper = Relationship(
back_populates = "cited_by",
sa_relationship_kwargs = {"foreign_keys": "Citation.cited_id"},
)
В качестве примечания, я думаю, что в этом случае было бы даже лучше использовать прокси-ассоциацию, чтобы иметь дополнительную прямую ссылку из статьи на все статьи, которые она цитирует и цитирует (без дополнительного «прыжка» через Citation объект), но я считаю, что в настоящее время это невозможно с SQLModel.
Спасибо большое @Daniil! Я уже начал изучать исходный код SQLModel и нашел sa_relationship_kwargs, но не знал, как правильно его использовать. Еще раз спасибо и удачи в пиаре :)