在Django中链接多个filter(),这是一个错误吗?

问题描述 投票:71回答:4

我一直认为在Django中链接多个filter()调用始终与在一次调用中收集它们相同。

# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)

但我在我的代码中遇到了一个复杂的查询集,而事实并非如此

class Inventory(models.Model):
    book = models.ForeignKey(Book)

class Profile(models.Model):
    user = models.OneToOneField(auth.models.User)
    vacation = models.BooleanField()
    country = models.CharField(max_length=30)

# Not Equivalent!
Book.objects.filter(inventory__user__profile__vacation=False).filter(inventory__user__profile__country='BR')
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

生成的SQL是

SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") INNER JOIN "library_inventory" T5 ON ("library_book"."id" = T5."book_id") INNER JOIN "auth_user" T6 ON (T5."user_id" = T6."id") INNER JOIN "library_profile" T7 ON (T6."id" = T7."user_id") WHERE ("library_profile"."vacation" = False  AND T7."country" = BR )
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") WHERE ("library_profile"."vacation" = False  AND "library_profile"."country" = BR )

具有链式filter()调用的第一个查询集连接Inventory模型两次,有效地在两个条件之间创建OR,而第二个查询集将这两个条件组合在一起。我期待第一个查询也会和两个条件相符。这是预期的行为还是Django中的一个错误?

相关问题Is there a downside to using ".filter().filter().filter()..." in Django?的答案似乎表明两个查询集应该是等价的。

django django-orm
4个回答
82
投票

我理解它的方式是它们在设计上略有不同(我当然可以进行修正):filter(A, B)将首先根据A进行过滤,然后根据B进行子过滤,而filter(A).filter(B)将返回与A'和'a匹配的行可能与B匹配的不同行

看一下这里的例子:

https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

尤其:

同时应用单个filter()调用内的所有内容来过滤掉符合所有这些要求的项目。连续的filter()调用进一步限制了对象集

...

在第二个例子中(filter(A).filter(B)),第一个过滤器将查询集限制为(A)。第二个过滤器将博客集进一步限制为(B)。由第二过滤器选择的条目可以与第一过滤器中的条目相同或不同


48
投票

在大多数情况下,这两种过滤方式是等效的,但是当基于ForeignKey或ManyToManyField查询对象时,它们略有不同。

来自the documentation的例子。

模型 博客到条目是一对多的关系。

from django.db import models

class Blog(models.Model):
    ...

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    pub_date = models.DateField()
    ...

对象 假设这里有一些博客和入口对象。

查询

Blog.objects.filter(entry__headline_contains='Lennon', 
    entry__pub_date__year=2008)
Blog.objects.filter(entry__headline_contains='Lennon').filter(
    entry__pub_date__year=2008)  

对于第一个查询(单个过滤器),它只匹配blog1。

对于第二个查询(链式过滤器一个),它过滤掉了blog1和blog2。 第一个过滤器将查询集限制为blog1,blog2和blog5;第二个过滤器将博客集进一步限制为blog1和blog2。

你应该意识到这一点

我们使用每个过滤器语句过滤博客项目,而不是条目项目。

所以,它不一样,因为Blog和Entry是多值关系。

参考:https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships 如果有问题,请纠正我。

编辑:由于1.6链接不再可用,因此将v1.6更改为v1.8。


4
投票

正如您在生成的SQL语句中看到的那样,差异不是某些人可能怀疑的“OR”。这是WHERE和JOIN的放置方式。

Example1(相同的连接表):

(来自https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships的例子)

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

这将为您提供所有具有两个条目的博客(entry_headline_contains ='Lennon')AND(entry__pub_date__year = 2008),这是您对此查询的期望。结果:预订{entry.headline:'生活的列侬',entry.pub_date:'2008'}

例2(链式)

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

这将涵盖示例1的所有结果,但会产生稍多的结果。因为它首先使用(entry_headline_contains ='Lennon')过滤所有博客,然后从结果过滤器过滤(entry__pub_date__year = 2008)。

不同之处在于它还会为您提供如下结果:预订{entry.headline:'Lennon',entry.pub_date:2000},{entry.headline:'Bill',entry.pub_date:2008}

在你的情况下

我认为这是你需要的:

Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

如果您想使用OR,请阅读:https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects


0
投票

有时你不想像这样加入多个过滤器:

def your_dynamic_query_generator(self, event: Event):
    qs \
    .filter(shiftregistrations__event=event) \
    .filter(shiftregistrations__shifts=False)

以下代码实际上不会返回正确的内容。

def your_dynamic_query_generator(self, event: Event):
    return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)

你现在可以做的是使用注释计数过滤器。

在这种情况下,我们计算属于特定事件的所有班次。

qs: EventQuerySet = qs.annotate(
    num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event))
)

之后,您可以按注释进行过滤。

def your_dynamic_query_generator(self):
    return Q(num_shifts=0)

此解决方案在大型查询集上也更便宜。

希望这可以帮助。

© www.soinside.com 2019 - 2024. All rights reserved.