Since it appears you already configured some rudimentary relationship that allowed the usage of join conditions, the next step is simply to actually use the Query.join() call.
My example below uses SQLAlchemy directly, so you may need to adapt the references to those specific to Flask-SQLALchemy (e.g. instead of creating a session you may instead use db.session, which I referenced from the Quickstart; also the Child and Toy classes I used inherit from a common sqlalchemy.ext.declarative.declarative_base() for the same reason, otherwise the same principles introduced below should be commonly applicable).
First, import and set up the classes - to keep this generalized, I will be using sqlalchemy directly as noted:
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') )
Note that we have removed the extra keyword arguments for the Child.toys relationship construct, as the specified 'dynamic' style of loading is incompatible with the query desired.
Then set up the engine and add your data provided from your question:
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()
With the data added, we can see that in the documentation for loading relationships from SQLAlchemy, there is a rather comprehensive set of relationship loading API, and the contains_eager is the suitable one for your use case, as it states "that the given attribute should be eagerly loaded from columns stated manually in the query." So let's see it in action:
filtered_children = session.query(Child).join(Child.toys).filter(
Toy.color=='Red'
).options(
contains_eager(Child.toys)
).all()
This query ensures that the toys relationship declared through the Child is joined with the query, and that we also filter by "Red" toys, with the option to indicate that Child.toys should be "eager loaded from columns stated manually in the query" such that it will become available through each of the returned child object.
Now, see that your most recent desired for loop over filtered_children and their toys produce your desired output:
for child in filtered_children:
for toy in child.toys:
print(f'{child.id} {child.name} {toy.id} {toy.color}')
The following output should be produced:
1 First 2 Red
2 Second 4 Red
If we had logging enabled, we will see that the following output that indicates the SELECT statement that was issued by SQLAlchemy's engine:
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')
Note that only a single SELECT ... JOIN query was done and no additional queries were made for each of the Child, which would result in significant performance impact.