我一直认为在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?的答案似乎表明两个查询集应该是等效的。
答案 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()
...
<强>物体强>
假设这里有一些博客和入口对象
查询
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的放置方式。
(来自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'}
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))
)
对于大型查询集,该解决方案也更便宜。
希望这会有所帮助。