如何在没有外键的情况下使用 “primaryjoin”?

huangapple go评论43阅读模式
英文:

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构造:

  1. 创建一个带有id=0的虚拟父级,以便没有父级的子级的ForeignKey有东西可以指向。

  2. 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:

  1. Create a dummy parent with id=0 so that a parentless child's ForeignKey has something to point to

  2. Give Child an autoincrement integer as PK so that parent_id may be NULL.

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.

huangapple
  • 本文由 发表于 2023年6月15日 21:53:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76483206.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定