Reputation: 22822
Let's say I have 2 classes: Foo and Bar. Assume that a Foo can have many Bars (via foo.Bars). Bar has an end_date to indicate when a bar has ended. Bars that have not ended are set to '9999-12-31' by default.
I've written two queries below to get a Foo but with different bar filters such that the first query gives me bars that have not ended; the second gives me bars that have ended.
today = date.today().strftime("%Y-%m-%d")
current = session.query(Foo)\
.join(Bar)\
.filter(
and_(
Foo.code=='MY_STRING',
Bar.end_date >= today,
)
)\
.options(contains_eager('Bars'))\
.one()
previous = session.query(Foo)\
.join(Bar)\
.filter(
and_(
Foo.code=='MY_STRING',
Bar.end_date < today,
)
)\
.options(contains_eager('Bars'))\
.one()
For some reason previous.Bars are the same as current.Bars even though we've filtered the bars to be different (at least that was my attempt). E.g. current.Bars
returns [bar1, bar2, bar3]
and previous.Bars
returns [bar1, bar2, bar3]
.
If I switch the two queries (i.e. put previous
before current
), then current's Bars are the same as previous.Bars. E.g. current.Bars returns [bar4, bar5, bar6]
and previous.Bars
returns [bar4, bar5, bar6]
.
Note that I'm 100% sure that the underlying data in previous.Bars should be different from current.Bars. I.e. I should get [bar4, bar5, bar6]
for previous.Bars
and [bar1, bar2, bar3]
for current.Bars
Can someone please explain why I'm getting the same results? How can I solve this? I realize it's probably better just to have one query.
Thanks in advance!
PS I'm using version 0.8
Upvotes: 2
Views: 414
Reputation: 76952
The problem line is .options(contains_eager('Bars'))
. It basically tells sqlachemy
that that query contains all Bars
for each foo
loaded. So when you access foo.Bars
, it does not query the database anymore, because it has taken those (filtered) bars from the join
and assumed them to be a full foo.Bars
relationship.
The next time you run the query, sqlalchemy realizes that the object Foo
is already loaded (including its relationship Bars
) and basically returns what it currently has in the session (memory).
You can work-around this by removing the object from the session using session.expunge(current)
just before you execute query for previous
, so that sqlalchemy
will reload it completely, but you have to be careful using current
instance after that because it is not part of the session anymore.
Better yet: since you need both sides anyway, just do this separation on python side easily:
today = datetime.datetime.today()#.strftime("%Y-%m-%d")
foo = (session.query(Foo)
.options(joinedload(Foo.Bars))
.filter(Foo.code=='MY_STRING')
.one()
)
prev_bars = [bar for bar in foo.Bars if bar.end_date < today]
curr_bars = [bar for bar in foo.Bars if bar.end_date >=today]
print foo
print prev_bars
print curr_bars
Upvotes: 2