Flask sqlalchemy 过滤器 objects 在每个 object 的关系中

Flask sqlalchemy filter objects in relationship for each object

如何在一个命令中过滤关系中的objects?

示例过滤器:我有 childrens 的列表,每个 children 都有 玩具。告诉我如何过滤每个 child 的玩具,以便列表 child.toys 只包含红色玩具。

class Child(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(40))
    toys = db.relationship('Toy', lazy='dynamic')


class Toy(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    color = db.Column(db.String(40))
    child_id = db.Column(db.Integer, db.ForeignKey('child.id') )
Child id Child name
1 First
2 Second
Toy id Toy color Toy child_id
1 Blue 1
2 Red 1
3 Orange 2
4 Red 2

python 列表中的所需输出:

Child id Child name Toy id Toy color
1 First 2 Red
2 Second 4 Red

编辑: 此 table 将由以下人员打印:

for child in filtered_children: 
    for toy in child.toys:
        print(f'{child.id} {child.name} {toy.id} {toy.color}')

动态关系加载器提供了正确的结果,但您必须在 for 循环中像这样迭代:

children = Child.query.all()
for child in children:
    child.toys = child.toys.filter_by(color='Red')
len( children[0].toys ) #should by one
len( children[1].toys ) #should by one

有没有办法在没有 for 循环的情况下从关系中过滤 objects?

编辑: 重新表述的问题:有没有一种方法可以在外部查询中应用过滤器,这样就不需要在 for 循环内进行额外的过滤,这样每个 child 中的每个 child.toys 属性循环将只包含红色玩具?

看来您已经配置了一些允许使用 join conditions, the next step is simply to actually use the Query.join() 调用的基本关系。

我下面的示例直接使用 SQLAlchemy,因此您可能需要调整对 Flask-SQLALchemy 特定引用的引用(例如,您可以使用 db.session,而不是创建会话,我从 Quickstart;还有ChildToy类我用inherit from a commonsqlalchemy.ext.declarative.declarative_base()也是同理,否则下面介绍的原理应该是通用的) .

首先,导入并设置 类 - 为了保持这种通用性,我将按照说明直接使用 sqlalchemy

from sqlalchemy import Column, Integer, String
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, contains_eager

Base = declarative_base()

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    name = Column(String(40))
    toys = relationship('Toy')

class Toy(Base):
    __tablename__ = 'toy'
    id = Column(Integer, primary_key=True)
    color = Column(String(40))
    child_id = Column(Integer, ForeignKey('child.id') )

请注意,我们删除了 Child.toys 关系构造的额外关键字参数,因为指定的 'dynamic' 加载样式与所需的查询不兼容。

然后设置引擎并添加根据您的问题提供的数据:

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add(Child(name='First'))
session.add(Child(name='Second'))
session.add(Toy(color='Blue', child_id=1))
session.add(Toy(color='Red', child_id=1))
session.add(Toy(color='Orange', child_id=2))
session.add(Toy(color='Red', child_id=2))
session.commit()

添加数据后,我们可以看到在从 SQLAlchemy 加载关系的文档中,有一个 rather comprehensive set of relationship loading API, and the contains_eager 适合您的用例,因为它指出“给定的属性应该是急切地从查询中手动声明的列中加载。”那么让我们看看它的实际效果:

filtered_children = session.query(Child).join(Child.toys).filter(
    Toy.color=='Red'
).options(
    contains_eager(Child.toys)
).all()

此查询确保通过 Child 声明的 toys 关系与查询连接,并且我们还按 "Red" 玩具进行过滤,并可选择表明 Child.toys 应该“急切地从查询中手动声明的列中加载”,这样它将通过每个返回的 child 对象变得可用。

现在,看看你最近想要的循环 filtered_children 和他们的 toys 产生你想要的输出:

for child in filtered_children:
    for toy in child.toys:
        print(f'{child.id} {child.name} {toy.id} {toy.color}')

应生成以下输出:

1 First 2 Red
2 Second 4 Red

如果我们启用了日志记录,我们将看到以下输出表明 SQLAlchemy 引擎发出的 SELECT 语句:

INFO:sqlalchemy.engine.base.Engine:SELECT toy.id AS toy_id, toy.color AS toy_color, toy.child_id AS toy_child_id, child.id AS child_id, child.name AS child_name 
FROM child JOIN toy ON child.id = toy.child_id 
WHERE toy.color = ?
INFO:sqlalchemy.engine.base.Engine:('Red',)
DEBUG:sqlalchemy.engine.base.Engine:Col ('toy_id', 'toy_color', 'toy_child_id', 'child_id', 'child_name')
DEBUG:sqlalchemy.engine.base.Engine:Row (2, 'Red', 1, 1, 'First')
DEBUG:sqlalchemy.engine.base.Engine:Row (4, 'Red', 2, 2, 'Second')

请注意,只执行了一个 SELECT ... JOIN 查询,没有对每个 Child 进行额外的查询,这将对性能产生重大影响。