Migrating from joinedload_all
to Avoid Duplicate Joins in SQLAlchemy
#11356
-
Hello everyone, I'm facing a challenge with transitioning away from the joinedload_all function in SQLAlchemy, which has been deprecated. This function was key in my services for loading related objects without causing duplicate joins, and it was used extensively. Now, replacing joinedload_all with chained joinedload() requires checking each query to ensure there are no redundant joins, which is risky and impractical for me given the number of queries involved. Here's a simple example of the issue: Imagine I have this query: query = session.query(Parent).join(Child).filter(Child.type == 'primary') Then I want to load the school & teacher using # Current usage with joinedload_all (deprecated)
query = session.query(Parent).join(Child).options(
joinedload_all('children.school'),
joinedload_all('children.teacher')
).filter(Child.type == 'primary')
# Recommended approach with chained joinedload()
query = session.query(Parent).join(Child).options(
joinedload('children').joinedload('school'),
joinedload('children').joinedload('teacher')
).filter(Child.type == 'primary') The issue is when 'children' is already joined manually, the new method might try to join 'children' again. This could lead to duplicate joins and is a significant concern as manually checking and updating every query is not feasible. I'm looking for advice or solutions that could help manage these changes more effectively, or if anyone has developed utility functions that mimic the old joinedload_all behavior but without the risk of duplicate joins. Any insights or shared experiences would be highly appreciated! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 8 replies
-
hi - edit: your joinedload_all() isn't really correct, it only accepts a single path at a time, not comma separated. joinedload_all() has no additional functionality compared to chained joinedload() calls, it's just a wrapper for the individual calls. The replacement for joinedload_all() is to use individual joinedload() calls only. you would not add query.join() into the mix, that's an entirely separate thing. The second query you have, remove the join(), and that's the equivalent: # a correct use of joinedload_all
query = session.query(Parent).options(joinedload_all('children.school'), joinedload_all('children.teacher'))
# *actual* Recommended approach with chained joinedload()
query = session.query(Parent).options(
joinedload('children').joinedload('school'),
joinedload('children').joinedload('teacher')
) as for mixing joinedload and manual join() and avoiding redundant joins, see the section https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html#routing-explicit-joins-statements-into-eagerly-loaded-collections |
Beta Was this translation helpful? Give feedback.
-
I updated the example on the question if you can check it again. |
Beta Was this translation helpful? Give feedback.
that's just one option cancelling the other out - an idiosyncrasy in 1.3 is causing the joinedload_all() version to place the contains_eager at a higher priority. reverse the order for both and put the contains_eager() last, so it overrides whatever is there for "bs":
now you get the same query.
Bigger picture you should not use two options that match the same path. if you want to ignore a path token, use
defaultload()
like this: