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

时间:2011-11-17 09:17:23

标签: django django-orm

我一直认为在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?的答案似乎表明两个查询集应该是等效的。

6 个答案:

答案 0 :(得分:81)

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

请看这里的例子:

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

特别:

  

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

...

  

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

答案 1 :(得分: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()
    ...

<强>物体
假设这里有一些博客和入口对象 enter image description here

查询

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。

答案 2 :(得分: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)

这将为您提供包含一个条目的所有博客(条目_ 标题 _contains ='列侬')和(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_ 标题 _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

答案 3 :(得分:2)

来自Django docs

要处理这两种情况,Django有一致的处理filter()调用的方式。单个filter()调用中的所有内容将同时应用,以过滤出符合所有这些要求的项目。连续的filter()调用进一步限制了对象集,但是对于多值关系,它们适用于链接到主模型的任何对象,不一定适用于先前的filter()调用选择的那些对象。

  • 显然,在一个filter()中同时应用了多个条件。 那就是说:
objs = Mymodel.objects.filter(a=True, b=False)

将返回包含模型Mymodel中的原始数据的查询集,其中a=True AND b=False

  • 成功的filter()在某些情况下将提供相同的结果。正在执行:
objs = Mymodel.objects.filter(a=True).filter(b=False)

还将返回一个查询集,其中包含模型Mymodel中的原始数据,其中a=True AND b=False也是如此。由于您首先获得的查询集包含a=True记录,然后仅限于同时具有b=False的记录集。

  • 链接filter()的区别在于存在multi-valued relations时,这意味着您要遍历其他模型(例如文档中给出的Blog和Entry模型之间的示例)。据说在这种情况下(...) they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.

这意味着它将后继filter()直接应用于目标模型,而不是先前的filter()

如果我以docs为例:

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

请记住,过滤的是模型Blog,而不是Entry。因此它将独立对待2个filter()

例如,它将返回带有Blog的查询集,其中包含包含“ Lennon”的条目(即使它们不是来自2008年)和包含2008年的条目(即使其标题不包含“ Lennon”)

THIS ANSWER在解释中更进一步。和最初的问题类似。

答案 4 :(得分:1)

在评论中看到这个,我认为这是最简单的解释。

filter(A, B) 是 AND ; filter(A).filter(B) 是 OR

答案 5 :(得分:0)

有时候您不想像这样将多个过滤器结合在一起:

h5dump

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

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))
)

对于大型查询集,该解决方案也更便宜。

希望这会有所帮助。