在 Django 中链接多个 filter(),这是一个错误吗?
我总是假设在 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 中的错误?
相关问题的答案有缺点吗在 Django 中使用“.filter().filter().filter()...”? 似乎表明这两个查询集应该是等效的。
I always assumed that chaining multiple filter() calls in Django was always the same as collecting them in a single call.
# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)
but I have run across a complicated queryset in my code where this is not the case
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')
The generated SQL is
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 )
The first queryset with the chained filter()
calls joins the Inventory model twice effectively creating an OR between the two conditions whereas the second queryset ANDs the two conditions together. I was expecting that the first query would also AND the two conditions. Is this the expected behavior or is this a bug in Django?
The answer to a related question Is there a downside to using ".filter().filter().filter()..." in Django? seems to indicated that the two querysets should be equivalent.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我的理解是,它们在设计上有细微的不同(我当然愿意接受纠正):
filter(A, B)
将首先根据 A 进行过滤,然后根据 B 进行子过滤,而filter(A).filter(B)
将返回与 A 匹配的行以及与 B 匹配的可能不同的行。请查看此处的示例:
https://docs.djangoproject.com/en/ dev/topics/db/queries/#spanning-multi-valued-relationships
特别是:
......
The way I understand it is that they are subtly different by design (and I am certainly open for correction):
filter(A, B)
will first filter according to A and then subfilter according to B, whilefilter(A).filter(B)
will return a row that matches A 'and' a potentially different row that matches B.Look at the example here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
particularly:
...
这两种过滤方式在大多数情况下是等效的,但是当基于ForeignKey或ManyToManyField查询对象时,它们略有不同。
文档中的示例。
型号
博客到条目是一对多的关系。
对象
假设这里有一些博客和条目对象。
查询
对于第一个查询(单个过滤器),它仅匹配 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。
These two style of filtering are equivalent in most cases, but when query on objects base on ForeignKey or ManyToManyField, they are slightly different.
Examples from the documentation.
model
Blog to Entry is a one-to-many relation.
objects
Assuming there are some blog and entry objects here.
queries
For the 1st query (single filter one), it match only blog1.
For the 2nd query (chained filters one), it filters out blog1 and blog2.
The first filter restricts the queryset to blog1, blog2 and blog5; the second filter restricts the set of blogs further to blog1 and blog2.
And you should realize that
So, it's not the same, because Blog and Entry are multi-valued relationships.
Reference: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
If there is something wrong, please correct me.
Edit: Changed v1.6 to v1.8 since the 1.6 links are no longer available.
正如您在生成的 SQL 语句中所看到的,差异并不是某些人可能怀疑的“OR”。这就是 WHERE 和 JOIN 的放置方式。
示例1(相同的连接表):(
来自 的示例https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships)
这将为您提供所有具有one 条目同时包含 (entry_headline_contains='Lennon') 和 (entry__pub_date__year=2008),这就是您对该查询的期望。
结果:
Book with {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}
示例 2(链接)
这将覆盖示例 1 中的所有结果,但会生成稍微多一点的结果。因为它首先使用 (entry_headline_contains='Lennon') 过滤所有博客,然后从结果过滤器 (entry__pub_date__year=2008) 中过滤。
不同之处在于它还会为您提供如下结果:
通过 {entry.headline: 'Lennon', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008} 预订
根据您的情况,
我认为您需要的是这个:
如果您想使用 OR 请阅读:https://docs.djangoproject.com/ en/dev/topics/db/queries/#complex-lookups-with-q-objects
As you can see in the generated SQL statements the difference is not the "OR" as some may suspect. It is how the WHERE and JOIN is placed.
Example1 (same joined table) :
(example from https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships)
This will give you all the Blogs that have one entry with both (entry_headline_contains='Lennon') AND (entry__pub_date__year=2008), which is what you would expect from this query.
Result:
Book with {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}
Example 2 (chained)
This will cover all the results from Example 1, but it will generate slightly more result. Because it first filters all the blogs with (entry_headline_contains='Lennon') and then from the result filters (entry__pub_date__year=2008).
The difference is that it will also give you results like:
Book with {entry.headline: 'Lennon', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008}
In your case
I think it is this one you need:
And if you want to use OR please read: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects
来自 Django 文档 :
filter()
中的多个条件是同时应用的。这意味着执行 :
将返回一个带有来自模型
Mymodel
的原始查询集,其中a=True
ANDb=False
。filter()
将提供相同的结果。这样做:将返回一个带有来自模型
Mymodel
的原始查询集,其中a=True
ANDb=False
。由于您“首先”获得一个包含具有a=True
的记录的查询集,然后它仅限于同时具有b=False
的记录。多值关系
时,链接filter()
的差异就出现了,这意味着您正在经历其他模型(例如文档中给出的示例,博客之间和入门型号)。据说在这种情况下(...)它们适用于链接到主模型的任何对象,不一定是那些由先前的filter()调用选择的对象。
这意味着它适用直接在目标模型上的连续
filter()
,而不是在之前的filter()
如果我从文档中获取示例:
记住它是模型
Blog< /code> 是被过滤的,而不是
条目
。因此它将独立地处理 2 个filter()
。例如,它将返回博客的查询集,其中包含包含“Lennon”的条目(即使它们不是来自 2008 年)和来自 2008 年的条目(即使它们的标题不包含“Lennon”)
这个答案进一步解释了。原来的问题是类似的。
From Django docs :
filter()
are applied simultaneously.That means that doing :
will return a queryset with raws from model
Mymodel
wherea=True
ANDb=False
.filter()
, in some case, will provide the same result. Doing :will return a queryset with raws from model
Mymodel
wherea=True
ANDb=False
too. Since you obtain "first" a queryset with records which havea=True
and then it's restricted to those who haveb=False
at the same time.filter()
comes when there aremulti-valued relations
, which means you are going through other models (such as the example given in the docs, between Blog and Entry models). It is said that in that case(...) they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.
Which means that it applies the successives
filter()
on the target model directly, not on previousfilter()
If I take the example from the docs :
remember that it's the model
Blog
that is filtered, not theEntry
. So it will treat the 2filter()
independently.It will, for instance, return a queryset with Blogs, that have entries that contain 'Lennon' (even if they are not from 2008) and entries that are from 2008 (even if their headline does not contain 'Lennon')
THIS ANSWER goes even further in the explanation. And the original question is similar.
有时您不想像这样将多个过滤器连接在一起:
并且以下代码实际上不会返回正确的内容。
您现在可以做的是使用注释计数过滤器。
在这种情况下,我们计算属于某个事件的所有班次。
之后您可以通过注释进行过滤。
该解决方案在大型查询集上也更便宜。
希望这有帮助。
Sometimes you don't want to join multiple filters together like this:
And the following code would actually not return the correct thing.
What you can do now is to use an annotation count-filter.
In this case we count all shifts which belongs to a certain event.
Afterwards you can filter by annotation.
This solution is also cheaper on large querysets.
Hope this helps.
在评论中看到这个,我认为这是最简单的解释。
filter(A, B) 是 AND ; filter(A).filter(B) is OR
如果每个链接模型都满足这两个条件,则为 true
Saw this in a comment and I thought it was the simplest explanation.
filter(A, B) is the AND ; filter(A).filter(B) is OR
It's true if every linked model satisfies both conditions