如何使用带有关系的“活动”列?

时间:2018-10-25 02:19:44

标签: python python-3.x sqlalchemy

父母模型

class Parent(db.Model):
    children = db.relationship(Child, backref='parents')

儿童模特

class Child(db.Model):
    id = db.Column(db.INTEGER, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey(Parent.id))
    is_active = db.Column(db.Boolean, nullable=False, default=True)

假设ParentChild模型具有1:N的关系(显然在上面),我想根据activated的值仅获得is_active列。

是否有不需要额外过滤器来仅查询激活的子行的技巧?

例如,

假设Child表具有以下行:

enter image description here

查询如下:

p = Parent.query.filter(**some_conditions).one()
p.children  # Then the second row (id=2, is_active=False) shouldn't be here

最佳

1 个答案:

答案 0 :(得分:1)

首先,我不得不对您的模型进行一些修改,以使其成为MCVE

以下示例与此处的官方文档中的示例非常相似:Specifying Alternate Join Conditions,但关键是:

  

在构造联接时,relationship()的默认行为是,它使一侧的主键列的值等于另一侧的外键引用列的值。 我们可以使用primaryjoin参数将该条件更改为我们想要的任何方式 ...

因此,按照您的示例,默认情况下,SQLAlchemy将通过遵循Parent.id和Child.parent_id之间的外键路径来加入关系。但是,您可以将此连接条件设置为几乎任何所需的条件。例如:

class Parent(db.Model):
    id = db.Column(db.INTEGER, primary_key=True)
    children = db.relationship("Child", backref='parents')
    active_children = db.relationship("Child",
        primaryjoin="and_(Parent.id == Child.parent_id, "
                    "Child.is_active==True)")

如您所见,我们的primaryjoin参数现在是and_()函数调用的字符串表示形式,在该函数调用中,我们要求关系在两个模型之间的外键路径上都进行连接,来确定Child.is_active是否为True。在运行时,SQLAlchemy将评估该函数以发出用于从数据库获取相关行的SQL。

这是我的完整代码和基本测试:

import random

class Parent(db.Model):
    id = db.Column(db.INTEGER, primary_key=True)
    children = db.relationship("Child", backref='parents')
    active_children = db.relationship("Child",
        primaryjoin="and_(Parent.id == Child.parent_id, "
                    "Child.is_active==True)")

class Child(db.Model):
    id = db.Column(db.INTEGER, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey(Parent.id))
    is_active = db.Column(db.Boolean, nullable=False, default=True)

if __name__ == '__main__':
    db.drop_all()
    db.create_all()
    parent = Parent(id=1)
    db.session.add(parent)

    for _ in range(10):
        child = Child(parent_id=1, is_active=random.choice([True, False]))
        db.session.add(child)
    db.session.commit()

    print(parent.children)
    print(parent.active_children)
    assert all(c.is_active for c in parent.active_children)

一个警告是,您仍然可以将Child实例添加到Parent.active_children的{​​{1}}关系属性中,会话将对其进行跟踪,并在提交后将其持久化。这仅控制查询时从数据库中提取的结果。