ADDing Django's Q() objects and get two exclusive JOIN ONs
Asked Answered
H

1

0

So this is the scenario:

class Person(models.Model):
    ...
class Aktion(models.Model):
    ...

class Aktionsteilnahme(models.Model):
    person = models.ForeignKey(Person)
    aktion = models.ForeignKey(Aktion)

The problem now is, that I'm dynamically constructing rather complex queries based on Q()-objects. They end up like this:

Person.objects.filter(
    Q((Q()|Q(aktionsteilnahme__aktion=302))&
    (Q()|Q(aktionsteilnahme__aktion=547)))
)

which can (and automatically will) be reduced to:

Person.objects.filter(
    Q(aktionsteilnahme__aktion=302)&
    Q(aktionsteilnahme__aktion=547)
)

The problem now is, that this results in a SQL like this:

SELECT * FROM person
LEFT OUTER JOIN aktionsteilnahme ON ( person.id = aktionsteilnahme.person_id )
WHERE (aktionsteilnahme.aktion = 2890 AND aktionsteilnahme.aktion = 5924)

What I would actually need however is:

Person.objects.filter(Q(aktionsteilnahme__aktion=302))
    .filter(Q(aktionsteilnahme__aktion=547))

resulting in what I would actually need:

SELECT * FROM person
INNER JOIN aktionsteilnahme ON ( person.id = aktionsteilnahme.person_id )
INNER JOIN aktionsteilnahme T4 ON ( person.id = T4.person_id )
WHERE (aktionsteilnahme.aktion = 302 AND T4.aktion = 547)

I can't use the proposed solution though because all of it is gonna be OR'ed again.

I would have to be able to do something like:

Person.objects.filter(
    Q(
      Q(aktionsteilnahme__aktion=302))
      .filter(Q(aktionsteilnahme__aktion=547))
    )
    |
    Q(other_q_filters)
)
Heddie answered 23/5, 2015 at 22:24 Comment(0)
H
2

After fiddling some more I realized: Django QuerySets can be OR'ed.

My solution therefore is now to create something like this:

Person.objects.filter(Q(aktionsteilnahme__aktion=302))
      .filter(Q(aktionsteilnahme__aktion=547))
|
Person.objects.filter(Q(other_q_filters))

All the inner ANDs are now concatenated using filters, and the outer-most ORs are boolean | directly on the QuerySets.

Heads up! Requests get much slower due to inner subqueries always being completely evaluated (no more "limit 20")
and OR-ing QuerySets will result in multiple entries - so a final

(QuerySet | QuerySet | QuerySet).distinct()

will usually be necessary.

Heddie answered 23/5, 2015 at 22:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.