SQLAlchemy ORM无法与复合外键配合使用。

3
我想尝试构建一个包含多个相关模型的示例,就像以下所示。我们有一个模型B,它与模型C具有1:n的关系;然后我们有一个模型A,它与B有n:1的关系,并且与C也有n:1的关系。(C具有2列主键)
我尝试了这段代码:
class C(db.Model):
    __tablename__ = 'C'
    key1 = Column(Integer, primary_key=True)
    key2 = Column(Integer, primary_key=True)
    attr1 = Column(Date)
    attr2 = Column(Boolean)
    related_b = Column(Integer, ForeignKey('B.spam'))


class B(db.Model):
    __tablename__ = 'B'
    spam = Column(Integer, default=1, primary_key=True)
    eggs = Column(String, default='eggs')
    null = Column(String)
    n_relation = relationship(C, foreign_keys='C.related_b')


class A(db.Model):
    __tablename__ = 'A'
    foo = Column(String, default='foo', primary_key=True)
    bar = Column(String, default='bar', primary_key=True)
    baz = Column(String, default='baz')
    rel = relationship(B, foreign_keys='A.related_b')
    related_b = Column(Integer, ForeignKey('B.spam'))
    related_c1 = Column(Integer, ForeignKey('C.key1'))
    related_c2 = Column(Integer, ForeignKey('C.key2'))
    other_rel = relationship(C, foreign_keys=(related_c1, related_c2))

只是为了获得异常:

sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship A.other_rel - 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.

但是,我已经传递了那个参数。 我尝试了各种版本的该参数,通过名称指定A列、C列,通过直接引用指定C列,但似乎都没有影响这个错误。 我还尝试使用单个复合外键,像这样:

class A(db.Model):
    __tablename__ = 'A'
    foo = Column(String, default='foo', primary_key=True)
    bar = Column(String, default='bar', primary_key=True)
    baz = Column(String, default='baz')
    rel = relationship(B, foreign_keys='A.related_b')
    related_b = Column(Integer, ForeignKey('B.spam'))
    related_c1 = Column(Integer, ForeignKey('C.key1'))
    related_c2 = Column(Integer, ForeignKey('C.key2'))
    compound = ForeignKeyConstraint(('related_c1', 'related_c2'), ('C.key1', 'C.key2'))
    other_rel = relationship(C, foreign_keys=compound)

但是什么都没有改变。 我是不是错了什么,还是这是一个错误?(至少,错误信息不正确...)

如果你将A直接作为B的子级,而同时将A作为C的子级,我觉得这样做是多余的,因为C已经是B的子级。在A对象中拥有related_b并不必要,因为related_c也会确定A与哪个B相关联。(顺便说一句,更有意义的对象名称可能能更好地传达你的意图。) - undefined
关系A -> B和A -> C是独立的。每个A对象都有一个B“父母”和一个C“父母”(如果你愿意,可以称之为母亲和父亲)。反过来,B对象与几个C对象相关联(其中一个可能与A相关或者可能不相关)。它们可以是...母亲的朋友吗? - undefined
换句话说,A.other_rel.related_b可能等于A.related_b,也可能不等于A.related_b。 - undefined
我在SQLAlchemy中没有发现任何错误。这段代码对我来说运行良好。 - undefined
翻译文本:它有一些不同之处。如果我理解正确,在你的代码中,每个关系都是1:1的,而在我的代码中,存在n:1的关系。此外,在我的代码中,C具有一个由2列组成的主键,我不知道这是否是问题的一部分,但可能是。 - undefined
1个回答

6
在这里的问题是你必须在类的 __table_args__ 中声明 ForeignKeyConstraint(),而不是在类的主体中声明。
换句话说,以下代码将不会将外键约束应用于子表...
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String
from sqlalchemy.orm import declarative_base, relationship

connection_url = r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
engine = create_engine(connection_url)
Base = declarative_base()


class Parent(Base):
    __tablename__ = "tbl_parent"
    id1 = Column(Integer, primary_key=True)
    id2 = Column(Integer, primary_key=True)
    parent_name = Column(String(50))
    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "tbl_child"
    id = Column(Integer, primary_key=True, autoincrement=False)
    child_name = Column(String(50))
    parent_id1 = Column(Integer)
    parent_id2 = Column(Integer)
    # this does not work
    ForeignKeyConstraint(
        ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
    )
    parent = relationship(
        "Parent",
        foreign_keys="[Child.parent_id1, Child.parent_id2]",
        back_populates="children",
    )


Base.metadata.drop_all(engine)
engine.echo = True
Base.metadata.create_all(engine)

"""DDL emitted:
CREATE TABLE tbl_parent (
    id1 INTEGER NOT NULL, 
    id2 INTEGER NOT NULL, 
    parent_name VARCHAR(50), 
    PRIMARY KEY (id1, id2)
)
CREATE TABLE tbl_child (
    id INTEGER NOT NULL, 
    child_name VARCHAR(50), 
    parent_id1 INTEGER, 
    parent_id2 INTEGER, 
    PRIMARY KEY (id)
)
"""

...但这个确实会起作用...
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String
from sqlalchemy.orm import declarative_base, relationship

connection_url = r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
engine = create_engine(connection_url)
Base = declarative_base()


class Parent(Base):
    __tablename__ = "tbl_parent"
    id1 = Column(Integer, primary_key=True)
    id2 = Column(Integer, primary_key=True)
    parent_name = Column(String(50))
    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "tbl_child"
    # this works
    __table_args__ = (
        ForeignKeyConstraint(
            ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
        ),
    )
    id = Column(Integer, primary_key=True, autoincrement=False)
    child_name = Column(String(50))
    parent_id1 = Column(Integer)
    parent_id2 = Column(Integer)

    parent = relationship(
        "Parent",
        foreign_keys="[Child.parent_id1, Child.parent_id2]",
        back_populates="children",
    )


Base.metadata.drop_all(engine)
engine.echo = True
Base.metadata.create_all(engine)

"""DDL emitted:
CREATE TABLE tbl_parent (
    id1 INTEGER NOT NULL, 
    id2 INTEGER NOT NULL, 
    parent_name VARCHAR(50) NULL, 
    PRIMARY KEY (id1, id2)
)
CREATE TABLE tbl_child (
    id INTEGER NOT NULL, 
    child_name VARCHAR(50) NULL, 
    parent_id1 INTEGER NULL, 
    parent_id2 INTEGER NULL, 
    PRIMARY KEY (id), 
    FOREIGN KEY(parent_id1, parent_id2) REFERENCES tbl_parent (id1, id2)
)
"""

参考:

https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#orm-declarative-table-configuration


非常感谢,问题已解决。不过,我更希望SQLA能提供一个更清晰的错误信息。确实,我传递了那个参数,但它却无视了它的无效性。 - undefined
2
这里的问题是你提供了一个__tablename__,所以你必须在__table_args__中声明ForeignKeyConstraint,而不是在类的主体中。然而,这两者并没有直接关联。 - undefined
@IljaEverilä - 感谢你的建议。 - undefined
这适用于SQLAlchemy 2.0。谢谢。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接