SQLAlchemy:多个表的外键

问题描述 投票:0回答:3

让我们考虑 3 个表:

  • 书籍
  • 美国作家
  • 英国作家

每本书都有一个其作者的外键,可以在美国表中,也可以在英国表中。

如何在 SQLAlchemy 中实现这样的外键条件?

我想要一个单独的列来处理链接。


到目前为止,我的方法是创建一个抽象类

Author
AmericanAuthor
BritishAuthor
都继承自该抽象类,并且让
Book
的外键指向父级。

class Author(Model):
    __abstract__ = True
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)

class AmericanAuthor(Author):
    __tablename__ = 'american_author'
    # some other stuff

class BritishAuthor(Author):
    __tablename__ = 'british_author'
    # some other stuff

class Book(Model):
    __tablename__ = 'book'
    title = db.Column(db.String)
    author_id = db.Column(db.Integer, db.ForeignKey("author.id"))

失败并出现以下错误:

sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'books.author_id' could not find table 'author' with which to generate a foreign key to target column 'id'

这完全有道理,考虑到

author
是抽象的......

python postgresql sqlalchemy
3个回答
13
投票

虽然

@property
装饰器可以在应用程序中工作,但使用
@hybrid_property
包中的
sqlalchemy.ext.hybrid
可能会更好。通过这种方式,您将能够像任何普通属性一样过滤该属性。

您的 Book 类将如下所示:

class Book(Model):
    __tablename__ = 'book'
    title = db.Column(db.String)
    american_author_id = db.Column(db.Integer, db.ForeignKey("american_author.id"), nullable=True)
    british_author_id = db.Column(db.Integer, db.ForeignKey("british_author.id"), nullable=True)

    @hybrid_property
    def author_id(self):
        return self.american_author_id or self.british_author_id

11
投票

我认为您无法使用同一列与两个不同的表建立关系。

尝试创建两个不同的列(“american_author_id”和“british_author_id”),然后创建一个 @property“author”,返回不为 NULL 的作者。

这样你就可以让作者使用:

mybook.author


0
投票

您应该能够通过使用

AbstractConcreteBase
primaryjoin
来解决这个问题。

https://docs.sqlalchemy.org/en/20/orm/inheritance.html

class Author(AbstractConcreteBase, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    name = db.Column(db.String)
    
    @declared_attr
    def books(cls) -> Mapped[List["Book"]]:
        return relationship(
            "Book",
            back_populates="author",
            primaryjoin=lambda: and_(foreign(Book.author_id) == cls.id)
        )


class AmericanAuthor(Author):
    __tablename__ = 'american_author'
    # some other stuff

    __mapper_args__ = {
        "polymorphic_identity": "american",
        "concrete": True,
    }

class BritishAuthor(Author):
    __tablename__ = 'british_author'
    # some other stuff

    __mapper_args__ = {
        "polymorphic_identity": "british",
        "concrete": True,
    }

class Book(Model):
    __tablename__ = 'book'
    title: Mapped[str]
    author_id: Mapped[int]  # Not a foreign key

    author: Mapped["Author"] = relationship(
        back_populates="books",
        primaryjoin="and_(foreign(Book.author_id)==Author.id)"
    )

有了这个,你应该能够做到

book.author
author.books

© www.soinside.com 2019 - 2024. All rights reserved.