Django - 如何使用QuerySet过滤来获取对象的子集?

时间:2013-08-26 16:15:52

标签: python django

根据documentation

  

filter(** kwargs)返回包含匹配对象的新QuerySet   给定的查找参数。

     

查找参数(** kwargs)应采用上述格式   现场查找如下。多个参数通过AND连接在一起   基础SQL语句。

对我来说,它会返回原始集合中的项目子集。 但是我似乎错过了一些东西,因为下面的例子并不像我期望的那样:

>>> kids = Kid.objects.all()
>>> tuple(k.name for k in kids)
(u'Bob',)
>>> toys = Toy.objects.all()
>>> tuple( (t.name, t.owner.name) for t in toys)
((u'car', u'Bob'), (u'bear', u'Bob'))
>>> subsel = Kid.objects.filter( owns__in = toys )
>>> tuple( k.name for k in subsel )
(u'Bob', u'Bob')
>>> str(subsel.query)
'SELECT "bug_kid"."id", "bug_kid"."name" FROM "bug_kid" INNER JOIN "bug_toy" ON ("bug_kid"."id" = "bug_toy"."owner_id") WHERE "bug_toy"."id" IN (SELECT U0."id" FROM "bug_toy" U0)'

正如您在上面看到的那样, subsel 最终会返回重复的记录,这不是我想要的。我的问题是什么是正确的获取子集的方法? (注意:按定义设置不会出现多次出现的同一个对象)

解释为什么它的行为也很好,对于我来说 filter 意味着你用 filter()构建了什么-in函数在Python中。这是:采取满足要求的元素(换句话说,丢弃不符合要求的元素)。而这个定义似乎不允许引入/复制对象。

我知道可以完全 distinct(),但这仍然导致相当丑陋(可能比可能更慢)查询:

>>> str( subsel.distinct().query )
'SELECT DISTINCT "bug_kid"."id", "bug_kid"."name" FROM "bug_kid" INNER JOIN "bug_toy" ON ("bug_kid"."id" = "bug_toy"."owner_id") WHERE "bug_toy"."id" IN (SELECT U0."id" FROM "bug_toy" U0)'

我的 models.py 表示完整性:

from django.db import models

class Kid(models.Model):
    name = models.CharField(max_length=200)

class Toy(models.Model):
    name = models.CharField(max_length=200)
    owner = models.ForeignKey(Kid, related_name='owns')

修改

与@limelight聊天后得出结论是我的问题是我希望 filter()根据字典定义行事。并且它是如何在Python或任何其他理智的框架/语言中工作的。

更准确地说,如果我设置A = {x,y,z}并调用A.filter( <predicate> ),我不希望任何元素重复。使用Django的QuerySet,但它的行为如下:

A = {x,y,z}
A.filter( <predicate> )
# now A i.e. = {x,x}

首先,问题是不合适的方法名称(类似匹配()会更好)。 第二件事是我认为创建比Django允许的更有效的查询是可能的。我可能错了,如果我有一点时间,我可能会尝试检查是否属实。

2 个答案:

答案 0 :(得分:9)

这有点难看,但有效(没有任何类型安全):

toy_owners = Toy.objects.values("owner_id")  # optionally with .distinct()
Kid.objects.filter(id__in=toy_owners)

如果表现不成问题,我认为@limelights是对的。

PS!我在Django 1.6b2上测试了你的查询,得到了同样不必要的复杂查询。

答案 1 :(得分:2)

相反,DISTINCT可以使用GROUP BY(在django中注释)来获得不同的孩子。

toy_owners = Toy.objects.values_list("owner_id", flat=True).distinct()
Kid.objects.only('name').filter(pk__in=toy_owners).annotate(count=Count('owns'))