Filtering OR-combined queries

46 views
Skip to first unread message

Shaheed Haque

unread,
Apr 29, 2021, 8:43:27 PM4/29/21
to django...@googlegroups.com
Hi,

I have a query which ORs some selects on a single table together like this:

jurisdiction = 'aaa'
qs = Buss.objects.filter(jurisdiction=jurisdiction)
qs = qs | Buss.objects.filter(jurisdiction='xxx').exclude(name__in=qs.values_list('name', flat=True))
qs = qs | Buss.objects.filter(jurisdiction='yyy').exclude(name__in=qs.values_list('name', flat=True))

This seems to work just fine (and the raw SQL looks suitably complicated):

SELECT "paiyroll_buss"."id", "paiyroll_buss"."jurisdiction", "paiyroll_buss"."name", "paiyroll_buss"."description" FROM "paiyroll_buss" WHERE ("paiyroll_buss"."jurisdiction" = aaa OR ("paiyroll_buss"."jurisdiction" = xxx AND NOT ("paiyroll_buss"."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE U0."jurisdiction" = aaa))) OR ("paiyroll_buss"."jurisdiction" = yyy AND NOT ("paiyroll_buss"."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE (U0."jurisdiction" = aaa OR (U0."jurisdiction" = xxx AND NOT (U0."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE U0."jurisdiction" = aaa))))))))

Now, if I post-filter qs using something other than "aaa" (the first term above) like this:

   qs.filter('xxx)

then the resulting SQL has an 'AND "paiyroll_buss"."jurisdiction" = xxx' as one might expect. However, if I try to post-filter qs like this:

    qs.filter('aaa') # 'aaa' was the first term in the original trio of clauses

Then the formed SQL looks like this:

SELECT "paiyroll_buss"."id", "paiyroll_buss"."jurisdiction", "paiyroll_buss"."name", "paiyroll_buss"."description" FROM "paiyroll_buss" WHERE ("paiyroll_buss"."jurisdiction" = aaa OR ("paiyroll_buss"."jurisdiction" = xxx AND NOT ("paiyroll_buss"."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE U0."jurisdiction" = aaa))) OR ("paiyroll_buss"."jurisdiction" = yyy AND NOT ("paiyroll_buss"."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE (U0."jurisdiction" = aaa OR (U0."jurisdiction" = xxx AND NOT (U0."name" IN (SELECT U0."name" FROM "paiyroll_buss" U0 WHERE U0."jurisdiction" = aaa))))))))

i.e. just like the original 3-clause query. (I realise that in this case, the query degenerates to the first term). What am I missing?

Any clues appreciated.

Thanks, Shaheed




Sebastian Jung

unread,
Apr 30, 2021, 6:53:18 AM4/30/21
to django...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CAHAc2jcLVcFFiXRZtX_iGTJ2YJ0CNsObRFDyw1Ss6i3nV-X-vw%40mail.gmail.com.

Shaheed Haque

unread,
Apr 30, 2021, 9:58:53 AM4/30/21
to django...@googlegroups.com
Hi,

On Fri, 30 Apr 2021 at 11:52, Sebastian Jung <sebasti...@gmail.com> wrote:

Thanks for the response, but the OR is not the problem: it works just fine. (FWIW, I used the "|" form rather than the Q() form because it is not clear to me how one can express a .exclude() when using Q()). The problem here is that the result of the OR cannot be reliably .filter()d.

As I tried to explain, I can see that in some cases, a .filter() of the OR'd result does indeed add extra SQL to the query, whereas in others it does not:

    qs_or_result.filter(a='b')

This works for some values of "a" but not others, I can use this to achieve the same effect:

    Buss.objects.filter(a='b').intersection(qs_or_result)

though it is rather clumsy!!! FWIW, this is with Django 3.2. I'm inclined to think this is a bug in the ORM, though I have not dived into the code to track it down.

Thanks, Shaheed
 

Simon Charette

unread,
May 3, 2021, 11:10:24 AM5/3/21
to Django users
Hello Shaheed,

I didn't look at your issue into details but since it involves exclude, subqueries, an queryset combination and only manifests itself on Django 3.2 I wouldn't be surprised if it was related to some regressions in this area that are fixed in Django 3.2.1 which is meant to be released tomorrow


Cheers,
Simon

Shaheed Haque

unread,
May 4, 2021, 8:24:19 AM5/4/21
to django...@googlegroups.com
Simon,

Thanks for the heads up. At first glance, the release notes don't *quite* seem to match what I see but as you suggest, I will certainly check the new release before filing an issue.

TTFN, Shaheed

Shaheed Haque

unread,
May 5, 2021, 12:02:21 PM5/5/21
to django...@googlegroups.com
After testing with 3.2.1, I filed https://code.djangoproject.com/ticket/32717.

Shaheed Haque

unread,
May 12, 2021, 6:27:02 AM5/12/21
to django...@googlegroups.com
Just to close the loop on this, the defect in https://code.djangoproject.com/ticket/32717 is on its way to being resolved, but my original reason for using OR'd queries rather than Q() expressions was that I had not quite grokked the use of ~Q() to implement .exclude(). Simon kindly pointed out the code could have been written like this:

def jurisdiction_qs(for_jurisdiction):
    filter_ = Q(jurisdiction=for_jurisdiction)
    if for_jurisdiction != 'Universal':
        filter_ |= Q(jurisdiction='Universal') & ~Q(
            name__in=Buss.objects.filter(filter_).values_list('name', flat=True)
        )
    if for_jurisdiction != 'Company':
       filter_ |= Q(jurisdiction='Company') & ~Q(
            name__in=Buss.objects.filter(filter_).values_list('name', flat=True)
        )
    return Buss.objects.filter(filter_)

Notice the use of "& ~Q()"...

Thanks, Shaheed
Reply all
Reply to author
Forward
0 new messages