SQLAlchemy ORM 不适用于复合外键
SQLAlchemy ORM not working with composite foreign keys
我正在尝试使用多个相关模型构建示例,如下所示。
我们有一个与模型 C 有 1:n 关系的模型 B;然后我们有一个模型 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)
但没有任何改变。
我是误会了什么还是错误? (至少,错误信息是不正确的...)
这里的问题是您必须在 __table_args__
中声明 ForeignKeyConstraint
,而不是在 class.
的正文中声明
换句话说,下面的代码不会将外键约束应用于子 table ...
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, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
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)
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.create_all(engine)
""" console output:
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50),
PRIMARY KEY (id1, id2)
)
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,900 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50),
parent_id1 INTEGER,
parent_id2 INTEGER,
PRIMARY KEY (id)
)
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine COMMIT
"""
...但这将 ...
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, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
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"
__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.create_all(engine)
""" console output:
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50) NULL,
PRIMARY KEY (id1, id2)
)
2020-04-30 07:52:43,771 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,776 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine
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)
)
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,802 INFO sqlalchemy.engine.Engine COMMIT
"""
参考:
https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#orm-declarative-table-configuration
我正在尝试使用多个相关模型构建示例,如下所示。 我们有一个与模型 C 有 1:n 关系的模型 B;然后我们有一个模型 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)
但没有任何改变。 我是误会了什么还是错误? (至少,错误信息是不正确的...)
这里的问题是您必须在 __table_args__
中声明 ForeignKeyConstraint
,而不是在 class.
换句话说,下面的代码不会将外键约束应用于子 table ...
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, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
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)
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.create_all(engine)
""" console output:
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50),
PRIMARY KEY (id1, id2)
)
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,900 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50),
parent_id1 INTEGER,
parent_id2 INTEGER,
PRIMARY KEY (id)
)
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine COMMIT
"""
...但这将 ...
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, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
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"
__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.create_all(engine)
""" console output:
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50) NULL,
PRIMARY KEY (id1, id2)
)
2020-04-30 07:52:43,771 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,776 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine
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)
)
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,802 INFO sqlalchemy.engine.Engine COMMIT
"""
参考:
https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#orm-declarative-table-configuration