一尘不染

Django 1.11注释子查询聚合

django

这是我目前最喜欢使用的一项前沿功能,并且很快就会消失。我想将子查询聚合注释到现有查询集上。在1.11之前执行此操作意味着自定义SQL或修改数据库。这是this的文档以及其中的示例:

from django.db.models import OuterRef, Subquery, Sum
comments = Comment.objects.filter(post=OuterRef('pk')).values('post')
total_comments = comments.annotate(total=Sum('length')).values('total')
Post.objects.filter(length__gt=Subquery(total_comments))

他们在总体上进行注释,这对我来说似乎很奇怪,但是无论如何。

我正在为此而苦苦挣扎,所以我将其沸腾回到我拥有数据的最简单的真实示例中。我有Carpark,其中包含许多Space。使用Book→Authorif会使你更快乐,但是-暂时-我仅想使用Subquery* 注释相关模型的数量。

spaces = Space.objects.filter(carpark=OuterRef('pk')).values('carpark')
count_spaces = spaces.annotate(c=Count('*')).values('c')
Carpark.objects.annotate(space_count=Subquery(count_spaces))

这给了我一个可爱,ProgrammingError: more than one row returned by a subquery used as an expression并且在我的脑海中,这个错误是很合理的。子查询返回带注释的总数的空格列表。

该示例建议发生某种魔术,最后我得到一个可以使用的数字。但这不是在这里发生吗?如何注释汇总的子查询数据?

嗯,正在向查询的SQL添加一些内容…
我建立了一个新的停车场/太空模型,它起作用了。因此,下一步是弄清楚是什么使我的SQL中毒了。在Laurent的建议下,我看了一下SQL,并尝试使其更像他们在答案中发布的版本。这是我发现真正问题的地方:

SELECT "bookings_carpark".*, (SELECT COUNT(U0."id") AS "c"
FROM "bookings_space" U0
WHERE U0."carpark_id" = ("bookings_carpark"."id")
GROUP BY U0."carpark_id", U0."space"
)
AS "space_count" FROM "bookings_carpark";

我已经突出显示了它,但这是该子查询的GROUP BY ... U0."space"。由于某种原因,它们都在重新调整。调查仍在继续。

编辑2:好吧,只要查看子查询SQL,通过through我就可以看到第二组

In [12]: print(Space.objects_standard.filter().values('carpark').annotate(c=Count('*')).values('c').query)
SELECT COUNT(*) AS "c" FROM "bookings_space" GROUP BY "bookings_space"."carpark_id", "bookings_space"."space" ORDER BY "bookings_space"."carpark_id" ASC, "bookings_space"."space" ASC

编辑3:好吧!这两种模型都有排序顺序。这些被传送到子查询。这些订单使我的查询膨胀并中断了查询。

我想这可能是在Django一个错误,但短于这两种模式移除元ORDER_BY的,有没有什么办法可以不排序在querytime查询?


阅读 720

收藏
2020-03-27

共2个答案

一尘不染

也可以创建的子类Subquery,以更改其输出的SQL。例如,你可以使用:

class SQCount(Subquery):
    template = "(SELECT count(*) FROM (%(subquery)s) _count)"
    output_field = models.IntegerField()

然后,你将像使用原始Subquery类一样使用它:

spaces = Space.objects.filter(carpark=OuterRef('pk')).values('pk')
Carpark.objects.annotate(space_count=SQCount(spaces))

你可以将此技巧(至少在postgres中)与一系列聚合函数结合使用:我经常使用它来构建值数组或求和。

2020-03-27
一尘不染

我只需要从模型中删除规定的元顺序。你可以通过.order_by()在子查询中添加一个空白来实现。在我的代码中,这意味着:

spaces = Space.objects.filter(carpark=OuterRef('pk')).order_by().values('carpark')
count_spaces = spaces.annotate(c=Count('*')).values('c')
Carpark.objects.annotate(space_count=Subquery(count_spaces))
2020-03-27