-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
DisjointEagerLoading
Michael Bayer edited this page Sep 20, 2024
·
7 revisions
The feature described in this entry is included in SQLAlchemy for
simple cases using "select IN eager loading"
described at https://docs.sqlalchemy.org/orm/loading_relationships.html .
This recipe can still be useful for special cases where only a specific SELECT
for related items is desired
For special cases where a very specific kind of "SELECT" is desired for the collection of items, the general technique to load a set of objects, and related objects, and then to "knit" them together manually is as follows:
from itertools import groupby
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import attributes
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relation
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
# parent
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
data = Column(String)
children = relation("Child", collection_class=set)
# child
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
data = Column(String)
parent_id = Column(Integer, ForeignKey("parent.id"))
def __repr__(self):
return "Child(%r)" % self.data
# setup
engine = create_engine("sqlite://", echo=True)
Base.metadata.create_all(engine)
session = sessionmaker(engine)()
# test data
session.add_all(
[
Parent(
data="parent %d" % i,
children=set(
[Child(data="child %d parent %d" % (j, i)) for j in range(50)]
),
)
for i in range(100)
]
)
session.commit()
# query for Parent objects
q = session.query(Parent).filter(Parent.id.between(28, 39)).limit(12)
# load them
parents = q.all()
# create *any query desired* to load the child objects. Here we will
# reuse the original query as a subquery.
subq = q.subquery()
child_q = session.query(Child).join(subq, subq.c.id == Child.parent_id)
# iterate the query and group into collections keyed by parent.id. this
# particular approach relies upon the objects being ordered by parent_id, but
# there are other approaches.
child_q = child_q.order_by(Child.parent_id)
children = dict(
(k, list(v))
for k, v in groupby(
child_q,
lambda x: x.parent_id,
)
)
# piece together using set_committed_value
for p in parents:
attributes.set_committed_value(p, "children", children.get(p.id, ()))