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

我总是假设在 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,而第二个查询集将两个条件放在一起。我希望第一个查询也会使用 AND 这两个条件。这是预期的行为还是 Django 中的 bug?

相关问题 在 Django 中使用“ . filter () . filter () . filter () ...”有什么缺点吗?的答案似乎表明这两个查询集应该是等价的。

171493 次浏览

我的理解是,它们在设计上有微妙的不同(我当然愿意纠正) : 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)。第二个筛选器选择的条目可能与第一个筛选器中的条目相同,也可能不相同。`

正如您可以在生成的 SQL 语句中看到的那样,区别并不像有些人所怀疑的那样是“ OR”。它是 WHERE 和 JOIN 的放置方式。

例子1(同一合并表) :

(例子来自 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 _ 头条 _ include = ‘ Lennon’)和(entry _ _ pub _ date _ _ year = 2008)的博客,这正是您期望从这个查询得到的。 结果: 带{ entry.header: ‘ Life of Lennon’,entry.pub _ date: ‘2008’}的书

例二(链接)

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

这将涵盖示例1中的所有结果,但是它将生成稍微多一点的结果。因为它首先使用(entry _ 头条 _ include = ‘ Lennon’)过滤所有 blog,然后从结果过滤器(entry _ pub _ date _ _ year = 2008)过滤所有 blog。

不同之处在于,它还会给你带来这样的结果: 带有{ entry.header: ‘ 列侬’,entry.pub _ date: 2000} ,{ entry.header: ‘ Bill’,entry.pub _ date: 二零零八年}的图书

对你来说

我觉得你需要的是这个:

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

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

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

来自 文件的例子。

模特
Blog to Entry 是一对多的关系。

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 和 entry 对象。
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)
    

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

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

你应该意识到这一点

我们使用每个过滤器语句过滤 Blog 项,而不是 Entry 项。

因此,这是不一样的,因为 Blog 和 Entry 是多值关系。

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

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

有时候你不想像这样把多个过滤器连接在一起:

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)

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

希望这个能帮上忙。

来自 姜戈医生:

为了处理这两种情况,Django 有一种处理 filter ()调用的一致方法。单个 filter ()调用中的所有内容都被同时应用,以筛选出匹配所有这些需求的项。连续的 filter ()调用进一步限制了对象集,但是对于多值关系,它们应用于链接到主模型的任何对象,而不一定是那些由早期 filter ()调用选择的对象。

  • 可以清楚地说,一个 filter()中的多个条件是同时应用的。 这意味着:
objs = Mymodel.objects.filter(a=True, b=False)

将返回一个带有模型 Mymodel原始数据的查询集,其中 a=True 还有 b=False

  • 在某些情况下,连续的 filter()将提供相同的结果:
objs = Mymodel.objects.filter(a=True).filter(b=False)

将返回一个查询集与原始从模型 Mymodel其中 a=True 还有 b=False太。因为您获得“首先”一个查询集,其中包含有 a=True的记录,然后它仅限于那些同时拥有 b=False的记录。

  • 当有 multi-valued relations时,链接 filter()的区别就出现了,这意味着您要遍历其他模型(例如文档中给出的示例,在 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()

如果我从文档中举个例子:

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

记住,被过滤的是模型 Blog,而不是模型 Entry。所以它将独立地处理2个 filter()

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

这个答案在解释上更进一步,原来的问题也是类似的。

我在一条评论中看到了这一点,我认为这是最简单的解释。

Filter (A,B)是 AND; filter (A)。 filter (B)是 OR

如果每个链接模型都满足这两个条件,那么这是正确的