Как реализовать таблицу «Цитирование»? (используя SQLModel или SQLAlchemy)

Я борюсь с реализацией концепции «цитирования научной статьи» в 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", но не знаю, как это исправить.


Чтобы воспроизвести ошибку:

  • Я использую Python 3.10.5;
  • единственная зависимость 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 тоже подойдет.

Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
Тонкая настройка GPT-3 с помощью Anaconda
Тонкая настройка GPT-3 с помощью Anaconda
Зарегистрируйте аккаунт Open ai, а затем получите ключ API ниже.
1
0
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обработка нескольких возможных 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, но не знал, как правильно его использовать. Еще раз спасибо и удачи в пиаре :)

chc 15.02.2023 14:23

Другие вопросы по теме