SQLAlchemy ORM 无法使用复合外键

giu*_*tte 3 python orm sqlalchemy foreign-keys relationship

我正在尝试使用几个相关模型构建一个示例,如下所示。我们有一个模型 B 与模型 C 具有 1:n 关系;那么我们就有一个模型 A,与 B 具有 1 关系,与 C 具有 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))
Run Code Online (Sandbox Code Playgroud)

只是为了得到例外:

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.
Run Code Online (Sandbox Code Playgroud)

但是嘿,我已经通过了这个论点。我尝试了该参数的各种版本,通过名称指定 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)
Run Code Online (Sandbox Code Playgroud)

但一切都没有改变。我是否弄错了什么或者这是一个错误?(至少,错误消息不正确......)

Gor*_*son 6

这里的问题是你必须在类ForeignKeyConstraint()中声明__table_args__,而不是在类的主体中。

\n

换句话说,以下代码不会将外键约束应用于子表 \xe2\x80\xa6

\n
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String\nfrom sqlalchemy.orm import declarative_base, relationship\n\nconnection_url = r"mssql+pyodbc://@.\\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"\nengine = create_engine(connection_url)\nBase = declarative_base()\n\n\nclass Parent(Base):\n    __tablename__ = "tbl_parent"\n    id1 = Column(Integer, primary_key=True)\n    id2 = Column(Integer, primary_key=True)\n    parent_name = Column(String(50))\n    children = relationship("Child", back_populates="parent")\n\n\nclass Child(Base):\n    __tablename__ = "tbl_child"\n    id = Column(Integer, primary_key=True, autoincrement=False)\n    child_name = Column(String(50))\n    parent_id1 = Column(Integer)\n    parent_id2 = Column(Integer)\n    # this does not work\n    ForeignKeyConstraint(\n        ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]\n    )\n    parent = relationship(\n        "Parent",\n        foreign_keys="[Child.parent_id1, Child.parent_id2]",\n        back_populates="children",\n    )\n\n\nBase.metadata.drop_all(engine)\nengine.echo = True\nBase.metadata.create_all(engine)\n\n"""DDL emitted:\nCREATE TABLE tbl_parent (\n    id1 INTEGER NOT NULL, \n    id2 INTEGER NOT NULL, \n    parent_name VARCHAR(50), \n    PRIMARY KEY (id1, id2)\n)\nCREATE TABLE tbl_child (\n    id INTEGER NOT NULL, \n    child_name VARCHAR(50), \n    parent_id1 INTEGER, \n    parent_id2 INTEGER, \n    PRIMARY KEY (id)\n)\n"""\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\xa6 但这可以工作 \xe2\x80\xa6

\n
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String\nfrom sqlalchemy.orm import declarative_base, relationship\n\nconnection_url = r"mssql+pyodbc://@.\\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"\nengine = create_engine(connection_url)\nBase = declarative_base()\n\n\nclass Parent(Base):\n    __tablename__ = "tbl_parent"\n    id1 = Column(Integer, primary_key=True)\n    id2 = Column(Integer, primary_key=True)\n    parent_name = Column(String(50))\n    children = relationship("Child", back_populates="parent")\n\n\nclass Child(Base):\n    __tablename__ = "tbl_child"\n    # this works\n    __table_args__ = (\n        ForeignKeyConstraint(\n            ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]\n        ),\n    )\n    id = Column(Integer, primary_key=True, autoincrement=False)\n    child_name = Column(String(50))\n    parent_id1 = Column(Integer)\n    parent_id2 = Column(Integer)\n\n    parent = relationship(\n        "Parent",\n        foreign_keys="[Child.parent_id1, Child.parent_id2]",\n        back_populates="children",\n    )\n\n\nBase.metadata.drop_all(engine)\nengine.echo = True\nBase.metadata.create_all(engine)\n\n"""DDL emitted:\nCREATE TABLE tbl_parent (\n    id1 INTEGER NOT NULL, \n    id2 INTEGER NOT NULL, \n    parent_name VARCHAR(50) NULL, \n    PRIMARY KEY (id1, id2)\n)\nCREATE TABLE tbl_child (\n    id INTEGER NOT NULL, \n    child_name VARCHAR(50) NULL, \n    parent_id1 INTEGER NULL, \n    parent_id2 INTEGER NULL, \n    PRIMARY KEY (id), \n    FOREIGN KEY(parent_id1, parent_id2) REFERENCES tbl_parent (id1, id2)\n)\n"""\n
Run Code Online (Sandbox Code Playgroud)\n

参考:

\n

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

\n

  • “这里的问题是,您提供了一个 `__tablename__`,因此您必须在 `__table_args__` 中声明 `ForeignKeyConstraint`,而不是在类的主体中”似乎有点误导,因为一个不遵循另一个。 (2认同)