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

use*_*478 83 django django-orm

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

# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)
Run Code Online (Sandbox Code Playgroud)

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

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')
Run Code Online (Sandbox Code Playgroud)

生成的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 )
Run Code Online (Sandbox Code Playgroud)

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

相关问题的答案在Django中使用".filter().filter().filter()..."是否有缺点?似乎表明两个查询集应该是等价的.

Tim*_*ony 92

我理解它的方式是它们在设计上略有不同(我当然可以进行修正):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).由第二过滤器选择的条目可以与第一过滤器中的条目相同或不同

  • 这种行为虽然有记载,但似乎违反了最不惊讶的原则.当字段在同一模型上时,多个filter()的AND在一起,但在跨越关系时则一起OR. (14认同)
  • 过滤器(A,B)是AND过滤器(A).过滤器(B)是OR (11认同)
  • 这个答案是不正确的.它不是"OR".这句话"第二个过滤器将博客集合进一步限制为那些(B)." 明确提到"那也是(B)." 如果您在此特定示例中观察到与OR类似的行为,则并不一定意味着您可以概括您自己的解释.请查看"Kevin 3112"和"Johnny Tsang"的答案.我相信这些都是正确的答案. (6认同)
  • 我相信你在第一段中有错误的方法 - 过滤器(A,B)是AND情况('lennon'和文档中的2008),而过滤器(A).filter(B)是OR情况( 'lennon'OR 2008).当您查看问题中生成的查询时,这是有道理的..filter(A).filter(B)情况会创建两次连接,从而产生一个OR. (3认同)
  • 所以"进一步限制"意味着"限制性较小"? (2认同)
  • 是的,它记录在 Django 中。但是记录糟糕的设计并不能使它成为好的设计。在某些情况下,过滤器似乎是嵌套的。例如,在 DRF 中,我在视图集的 `get_queryset(self)` 方法中添加了一些过滤,并且还使用开箱即用的 DRF 过滤器后端。过滤器似乎是嵌套的,如果应用于同一模型,它可以很好地工作,但是一旦在相关模型上完成过滤,就会中断(按设计)。这绝对违反了最少惊讶原则(至少)。 (2认同)
  • @anouares-sayid 我相信你错了,`Q(A) | Q(B)` 表示或。 (2认同)

小智 53

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

文档中的示例.

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

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()
    ...
Run Code Online (Sandbox Code Playgroud)

对象
假设这里有一些博客和入口对象.
在此输入图像描述

查询

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)  
Run Code Online (Sandbox Code Playgroud)

对于第一个查询(单个过滤器),它只匹配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认同)
  • 非常好的示意图示例,阐明了两者之间的区别。 (2认同)

lbr*_*ris 10

来自Django 文档

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

  • 很明显,单个条件中的多个条件filter()同时应用。这意味着做:
objs = Mymodel.objects.filter(a=True, b=False)
Run Code Online (Sandbox Code Playgroud)

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

  • 在某些情况下,连续的filter()会提供相同的结果。正在做 :
objs = Mymodel.objects.filter(a=True).filter(b=False)
Run Code Online (Sandbox Code Playgroud)

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

  • filter()当存在 时,链接的差异就出现了multi-valued relations,这意味着您正在经历其他模型(例如文档中给出的博客和条目模型之间的示例)。据说在那种情况下(...) 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)
Run Code Online (Sandbox Code Playgroud)

请记住,过滤的是模型Blog,而不是Entry. 所以它将filter()独立对待这两个。

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

这个答案进一步解释了。原来的问题是类似的。


小智 6

正如您在生成的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)
Run Code Online (Sandbox Code Playgroud)

这将为您提供所有具有一个条目的所有Blog,同时具有(entry_ 标题 _contains ='Lennon')和(entry__pub_date__year = 2008),这是您从该查询中获得的期望。结果:预订时带有{entry.headline:'Lennon的生活',entry.pub_date:'2008'}

示例2(链接)

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
Run Code Online (Sandbox Code Playgroud)

这将涵盖示例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')
Run Code Online (Sandbox Code Playgroud)

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