英文:
How to use "primaryjoin" without a ForeignKey?
问题
我尝试建立一种关系,而不使用实际的ForeignKey
。原因是Child
实例可能有也可能没有关联的Parent
。如果没有关联的话,我希望parent_id
属性可以是一个不是实际父母ID的值。
当运行以下示例时,我会收到一个异常:
sqlalchemy.exc.NoForeignKeysError: 无法确定父/子关系上的表之间的连接条件 - 没有外键连接这些表。请确保引用列与ForeignKey或ForeignKeyConstraint相关联,或指定一个'primaryjoin'表达式。
嗯,我确实指定了primaryjoin
表达式,但似乎没有帮助。我相信我严格遵循了文档。
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', back_populates='parent')
class Child(Base):
__tablename__ = 'child'
parent_id = Column(Integer, primary_key=True, default=0)
other_id = Column(Integer, primary_key=True, default=0)
parent = relationship('Parent', back_populates='children',
primaryjoin='Parent.id == Child.parent_id',
foreign_keys=parent_id,
remote_side=Parent.id)
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
db = sessionmaker(engine)()
parent = Parent()
child = Child()
other = Child(other_id=1)
parent.children.append(child)
db.add(parent)
db.add(other)
db.commit()
英文:
I'm trying to establish a relationship without using an actual ForeignKey
. The reason is that a Child
instance may or may not have an associated Parent
. If it doesn't I want the parent_id
property to be able to be something that isn't an actual parent's ID.
When running the following example I get an Exception:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child
tables on relationship Parent.children - there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint,
or specify a 'primaryjoin' expression.
Well, I did specify a primaryjoin
expression, but it doesn't seem to help. I believe I followed the documentation precisely.
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', back_populates='parent')
class Child(Base):
__tablename__ = 'child'
parent_id = Column(Integer, primary_key=True, default=0)
other_id = Column(Integer, primary_key=True, default=0)
parent = relationship('Parent', back_populates='children',
primaryjoin='Parent.id == Child.parent_id',
foreign_keys=parent_id,
remote_side=Parent.id)
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
db = sessionmaker(engine)()
parent = Parent()
child = Child()
other = Child(other_id=1)
parent.children.append(child)
db.add(parent)
db.add(other)
db.commit()
答案1
得分: 1
我认为只需要设置外键,而不需要设置远程键。不过这可能会让人感到困惑。似乎你需要配置双方。
parent_child_conds = dict(primaryjoin="Parent.id == Child.parent_id", foreign_keys="Child.parent_id")
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', back_populates='parent', **parent_child_conds)
class Child(Base):
__tablename__ = 'child'
parent_id = Column(Integer, primary_key=True, default=0)
other_id = Column(Integer, primary_key=True, default=0)
parent = relationship('Parent', back_populates='children', **parent_child_conds)
英文:
I think setting the foreign key is only needed, not remote. It is confusing though. Seems you have to configure both sides.
parent_child_conds = dict(primaryjoin="Parent.id == Child.parent_id", foreign_keys="Child.parent_id")
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', back_populates='parent', **parent_child_conds)
class Child(Base):
__tablename__ = 'child'
parent_id = Column(Integer, primary_key=True, default=0)
other_id = Column(Integer, primary_key=True, default=0)
parent = relationship('Parent', back_populates='children', **parent_child_conds)
答案2
得分: 0
除了Ian提供的有趣答案之外,我考虑了另外两个选项,它们都使用了“正确”的ForeignKey
构造:
-
创建一个带有id=0的虚拟父级,以便没有父级的子级的ForeignKey有东西可以指向。
-
给
Child
一个自增整数作为主键,以便parent_id可以为NULL
。
最终,我选择了选项2。在我的示例中,这并没有太大的区别,但在实际应用程序中,Child的主键由两个指向其他表的FKs组成(可能指向空值),还有一些其他值,这变得太混乱了。几乎每当我想出一个多列主键时,最终我都会用一个整数自增的主键来替代它,并且加上一个额外的UniqueConstraint
。如果必要的话,还有一些处理合并的机制。
英文:
Besides the interesting answer given by Ian, I considered two more options that both use "proper" ForeignKey
constructs:
-
Create a dummy parent with id=0 so that a parentless child's ForeignKey has something to point to
-
Give
Child
an autoincrement integer as PK so that parent_id may beNULL
.
I ended up going with option 2. In my example it doesn't make much of a difference, but in the real app the Child had a PK comprised of two FKs into other tables (possibly pointing at nothing) and some other value which became just too messy. Almost always when I come up a multi-column PK I find myself eventually replacing it with an integer autoincrement one, and an additional UniqueConstraint
. And some extra mechanics dealing with merges, if necessary.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论