M.O.
M.O.

Reputation: 2911

SQLAlchemy filter children

I have two SQLAlchemy models, a parent and a child, with a relationship between them, and I would like to query the parent table and filter the children. An example that doesn't work:

from sqlalchemy import (
    Column,
    ForeignKey,
    Integer,
    Text,
    UniqueConstraint,
    select,
    create_engine,
)
from sqlalchemy.orm import relationship, Session, joinedload

from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()
engine = create_engine("sqlite://") # In memory DB for example, I'm using MySQL in reality


class Parent(Base):
    __tablename__ = "parent"

    id = Column(Integer, primary_key=True, autoincrement=True)
    text = Column(Text)

    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "child"

    id = Column(Integer, primary_key=True, autoincrement=True)
    parent_id = Column(Integer, ForeignKey("parent.id"), nullable=False)
    name = Column(Text)

    question = relationship("Parent", back_populates="children")


Base.metadata.create_all(engine)

with Session(engine) as db:
    p = Parent(
        text="a",
        children=[
            Child(name="foo"),
            Child(name="bar"),
       ]
    )
    db.add(p)
    db.commit()

with Session(engine) as db:
    parent = db.execute(
        select(Parent)
        .join(Child)
        .where(Child.name == "foo")
        .options(joinedload(Parent.children))
    ).scalars().first()

    print(parent.children)  # I get both children here

What I would like is to somehow filter Parent.children so I only get the child with name == "foo" (in my example, there might be thousands of children, so I'd like to filter in the database).

I know I can "reverse" the query, with select(Child)... and getting the relevant data, but if possible, I'd like to retain the object structure of one top level Parent object with a filtered list of children. Is this somehow possible?

Upvotes: 1

Views: 746

Answers (1)

M.O.
M.O.

Reputation: 2911

I figured it out, it’s described in the documentation here. So, what I needed was

from sqlalchemy.orm import contains_eager

with Session(engine) as db:
    parent = db.execute(
        select(Parent)
        .join(Child)
        .where(Child.name == "foo")
        .options(contains_eager(Parent.children))
        .execution_options(populate_existing=True)
    ).scalars().first()

    print(parent.children)  # Here I only get the one child

Upvotes: 2

Related Questions