Goin
Goin

Reputation: 3964

Error in encapsulates filters (Q)

If I have the next code:

class A(models.Model):

   .....

class B(models.Model):

    a = models.ManyToManyField(A)

The next queries get differents results:

B.objects.exclude(a__in=[7])

from django.db.models import Q
B.objects.exclude(Q(a__in=[7]))

Results:

Is it an error?, Is it known?

I add a verbose example, execute the next code

from django.contrib.auth.models import User, Group
u1 = User.objects.create(username='u1')
u2 = User.objects.create(username='u2')
u3 = User.objects.create(username='u3')
g1 = Group.objects.create(name='g1')
g2 = Group.objects.create(name='g2')
u1.groups.add(g1)
u2.groups.add(g2)
print User.objects.exclude(groups__in=[g1.pk])
print User.objects.exclude(Q(groups__in=[g1.pk]))

Upvotes: 1

Views: 188

Answers (2)

Goin
Goin

Reputation: 3964

Now this is fixed in Django:

https://code.djangoproject.com/ticket/17600

This was a django bug

Upvotes: 1

Chris Pratt
Chris Pratt

Reputation: 239430

I'm not sure i'd call it an "error", but the two versions do send unique queries (Django 1.3.1).

Without Q:

SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user"
WHERE NOT ("auth_user"."id" IN
             (SELECT U1."user_id"
              FROM "auth_user_groups" U1
              WHERE (U1."group_id" IN (2)
                     AND U1."user_id" IS NOT NULL)))

With Q:

SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user"
INNER JOIN "auth_user_groups" ON ("auth_user"."id" = "auth_user_groups"."user_id")
WHERE NOT ("auth_user_groups"."group_id" IN (2))

What's interesting is that if you use filter instead of exclude, they both send exactly the same query. Nevertheless, this may actually be intentional. Q is never really used by itself (there's no point), so the query is probably pre-optimized for additional AND/OR/NOT relations. Whereas, the version without Q is done, for all intents and purposes. If you really have a problem with this behavior, you can file a ticket, but I'd say just don't use Q when you only have one.

Upvotes: 2

Related Questions