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

发布于 2024-12-16 14:46:06 字数 2140 浏览 1 评论 0原文

我总是假设在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

送你一个梦 2024-12-23 14:46:06

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

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, while filter(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:

Everything inside a single filter() call is applied simultaneously to filter out items matching all those requirements. Successive filter() calls further restrict the set of objects

...

In this second example (filter(A).filter(B)), the first filter restricted the queryset to (A). The second filter restricted the set of blogs further to those that are also (B). The entries select by the second filter may or may not be the same as the entries in the first filter.`

帝王念 2024-12-23 14:46:06

这两种过滤方式在大多数情况下是等效的,但是当基于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()
    ...

对象
假设这里有一些博客和条目对象。
在此处输入图像描述

查询

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。

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.

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

objects
Assuming there are some blog and entry objects here.
enter image description here

queries

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)  
    

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

We are filtering the Blog items with each filter statement, not the Entry items.

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.

泅人 2024-12-23 14:46:06

正如您在生成的 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)

这将为您提供所有具有one 条目同时包含 (entry_headline_contains='Lennon') 和 (entry__pub_date__year=2008),这就是您对该查询的期望。
结果:
Book with {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}

示例 2(链接)

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

这将覆盖示例 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')

如果您想使用 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)

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

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)

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

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:

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

And if you want to use OR please read: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

小梨窩很甜 2024-12-23 14:46:06

来自 Django 文档

为了处理这两种情况,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()的差异就出现了,这意味着您正在经历其他模型(例如文档中给出的示例,博客之间和入门型号)。据说在这种情况下(...)它们适用于链接到主模型的任何对象,不一定是那些由先前的filter()调用选择的对象。

这意味着它适用直接在目标模型上的连续 filter() ,而不是在之前的 filter()

如果我从文档中获取示例:

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

记住它是模型 Blog< /code> 是被过滤的,而不是条目。因此它将独立地处理 2 个 filter()

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

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

From Django docs :

To handle both of these situations, Django has a consistent way of processing filter() calls. Everything inside a single filter() call is applied simultaneously to filter out items matching all those requirements. Successive filter() calls further restrict the set of objects, but for 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.

  • It is clearly said that multiple conditions in a single filter() are applied simultaneously.
    That means that doing :
objs = Mymodel.objects.filter(a=True, b=False)

will return a queryset with raws from model Mymodel where a=True AND b=False.

  • Successive filter(), in some case, will provide the same result. Doing :
objs = Mymodel.objects.filter(a=True).filter(b=False)

will return a queryset with raws from model Mymodel where a=True AND b=False too. Since you obtain "first" a queryset with records which have a=True and then it's restricted to those who have b=False at the same time.

  • The difference in chaining filter() comes when there are multi-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 previous filter()

If I take the example from the docs :

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

remember that it's the model Blog that is filtered, not the Entry. So it will treat the 2 filter() 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.

我的影子我的梦 2024-12-23 14:46:06

有时您不想像这样将多个过滤器连接在一起:

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)

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

希望这有帮助。

Sometimes you don't want to join multiple filters together like this:

def your_dynamic_query_generator(self, event: Event):
    qs \
    .filter(shiftregistrations__event=event) \
    .filter(shiftregistrations__shifts=False)

And the following code would actually not return the correct thing.

def your_dynamic_query_generator(self, event: Event):
    return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)

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.

qs: EventQuerySet = qs.annotate(
    num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event))
)

Afterwards you can filter by annotation.

def your_dynamic_query_generator(self):
    return Q(num_shifts=0)

This solution is also cheaper on large querysets.

Hope this helps.

往事随风而去 2024-12-23 14:46:06

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文