20

我想我错过了一些关于 Django 的 filter() 方法应该如何工作的非常基本和基本的东西。

使用以下模型:

class Collection(models.Model): 
    pass

class Item(models.Model):
    flag = models.BooleanField()
    collection =  models.ForeignKey(Collection)

通过调用问题底部的 populate() 函数提供的数据,尝试在 ./manage.py shell 中执行以下命令:

len(Collection.objects.filter(item__flag=True))

我的期望是这将打印“2”,这是至少有一个带有 flag=True 的项目的集合的数量。这种期望基于https://docs.djangoproject.com/en/1.5/topics/db/queries/#lookups-that-span-relationships上的文档,其中有一个示例说“此示例检索所有 Entry 对象一个名为“Beatles Blog”的博客”。

但是,上面的调用实际上打印了“6”,这是具有 flag=True 的 Item 记录的数量。但是,返回的实际对象是 Collection 对象。似乎它多次返回同一个 Collection 对象,对于每个带有 flag=True 的对应项记录一次。这可以通过以下方式确认:

queryset = Collection.objects.filter(item__flag=True)
queryset[0] == queryset[1]

打印 True。

这是正确的行为吗?如果是这样,理由是什么?如果这是预期的,文档可以被解释为严格正确,但它没有说每个对象可以多次返回。

这是一个相关示例,这似乎是非常令人惊讶(或完全错误)的行为。在自定义模型管理器添加了 exclude() 调用并且调用者随后添加了 filter() 的情况下,我发现了这一点:

from django.db.models import Count    
[coll.count for coll in Collection.objects.filter(item__flag=True).annotate(count=Count("item"))]
[coll.count for coll in Collection.objects.exclude(item=None).filter(item__flag=True).annotate(count=Count("item"))]

第一种情况打印“[2,4]”,但第二种情况打印“[8,16]”!!!

填充函数:

def populate():
    Collection.objects.all().delete()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
4

1 回答 1

20

事实证明,这有两个部分。首先是 distinct() 方法,文档说:

默认情况下,QuerySet 不会消除重复行。在实践中,这很少会成为问题,因为像 Blog.objects.all() 这样的简单查询不会引入重复结果行的可能性。但是,如果您的查询跨越多个表,则在评估 QuerySet 时可能会得到重复的结果。那是你使用 distinct() 的时候。

以下按预期输出“2”:

len(Collection.objects.filter(item__flag=True).distinct())

然而,这对我给出的更复杂的例子没有帮助,使用 annotate()。事实证明这是一个已知问题的实例:https ://code.djangoproject.com/ticket/10060 。

于 2013-06-16T11:37:39.710 回答