SQLAlchemy具有额外联接条件的多对多关系

时间:2019-06-09 21:13:32

标签: python filter sqlalchemy many-to-many outer-join

我在父母与孩子之间有很多对很多的关系。 在我的views.py中,我这样做:

parents = s.query(Parent)

在我的(Jinja2)模板中,我做类似的事情:

for parent in parents:
    print("parent {}".format(parent.name))
    if parent.children:
        for child in parent.children:
            print("- has child {}, age {}, {}" \
                .format(child.name, child.age, child.hair_color))

这表明所有有或没有孩子的父母都很好。

现在,我想拥有所有父母的相同列表(如上所述),但仅适用于7岁以上且金发的孩子。将会有父母,有孩子或没有孩子。 问题:如何在SQLAlchemy中而不是在普通SQL中做到这一点?

我的模特:

# many-to-many link table: parent - child
parent_mtm_child_table = Table('parent_mtm_child', Base.metadata,
    Column('parent_id', Integer, ForeignKey('parent.id')),
    Column('child_id', Integer, ForeignKey('child.id'))
)

class Parent(Base):
    __tablename__ = 'parent'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    # many-to-many relationship with child
    children = relationship(
        'Child', 
        secondary=parent_mtm_child_table,
        back_populates='parents')

class Child(Base):
    __tablename__ = 'child'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)
    hair_color = Column(String)
    # many-to-many relationship with parent
    parents = relationship(
        'Parent', 
        secondary=parent_mtm_child_table,
        back_populates='children')

john = Parent(name='John')
mary = Parent(name='Mary')
gina = Parent(name='Gina')
liam = Child(name='Liam', age=6, hair_color='brown')
emma = Child(name='Emma', age=8, hair_color='blond')
lauren = Child(name='Lauren', age=9, hair_color='blond')
john.children.append(liam)
john.children.append(emma)
mary.children.append(liam)
gina.children.append(lauren)
s.add_all([john, mary, gina, liam, emma, lauren])
s.commit()

下一个查询是错误的,但是给出了我可以检查的生成的SQL:

r = s.query(Parent).outerjoin(Parent.children).filter(Child.age >= 7)

SQL按预期显示了WHERE部分中的额外条件:

sql = 'SELECT parent.id AS parent_id, parent.name AS parent_name \
FROM parent LEFT OUTER JOIN (parent_mtm_child AS parent_mtm_child_1 
JOIN child ON child.id = parent_mtm_child_1.child_id) 
ON parent.id = parent_mtm_child_1.parent_id \
WHERE child.age >= 7'

将额外条件移到ON-部分后,sql变为:

sql = 'SELECT parent.id AS parent_id, parent.name AS parent_name, child.id AS child_id, child.name AS child_name, child.age AS child_age \
FROM parent \
LEFT OUTER JOIN (parent_mtm_child AS parent_mtm_child_1 \
JOIN child ON ((child.id = parent_mtm_child_1.child_id) AND (child.age >= 7)) ) \
ON parent.id = parent_mtm_child_1.parent_id'

现在执行原始sql:

with engine.connect() as con:
    r = con.execute(sql)
    for row in r:
        print("row = {}".format(row))

结果正确:

row = (1, 'John', 2, 'Emma', 8)
row = (2, 'Mary', None, None, None)
row = (3, 'Gina', 3, 'Lauren', 9)

所以可能一定有可能。

我如何在SQLAlchemy中使用ON-part的额外条件来执行此操作?

我也尝试了contains_eager:

r = s.query(Parent).join(Parent.children).\
            filter(Child.age >= 7).\
            options(contains_eager(Parent.children)).all()

但是它给出了错误的结果:

parent John
- has child Emma, age 8, blond
- has child Liam, age 6, brown
parent Mary
- has child Liam, age 6, brown
parent Gina
- has child Lauren, age 9, blond

任何帮助表示赞赏。

0 个答案:

没有答案