Django 1.11 Annotating a Subquery Aggregate
这是我目前最喜欢使用的一项前沿功能,并且很快就会消失。我想将子查询聚合注释到现有查询集上。在1.11之前执行此操作意味着自定义SQL或修改数据库。这是此文档以及其中的示例:
1 2 3 4 | 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)) |
他们在总体上进行注释,这对我来说似乎很奇怪,但是无论如何。
我正在为此而苦苦挣扎,所以我将其沸腾回到我拥有数据的最简单的真实示例中。我有
1 2 3 | 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)) |
这给了我一个可爱的
该示例建议发生某种魔术,最后我得到一个可以使用的数字。但这不是在这里发生吗?如何注释汇总的子查询数据?
嗯,正在向查询的SQL添加一些内容...
我建立了一个新的停车场/太空模型,它起作用了。因此,下一步就是弄清楚是什么毒害了我的SQL。在Laurent的建议下,我看了一下SQL,并尝试使其更像他们在答案中发布的版本。这是我发现真正问题的地方:
1 2 3 4 5 6 | 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"; |
我已经突出显示了它,但这是该子查询的
编辑2:好吧,只要查看子查询SQL,我就可以看到第二组吗?
1 2 | 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中的错误,但是在这两个模型上都没有删除Meta-order_by,有什么方法可以在查询时取消查询的排序吗?
*我知道我可以为这个示例添加一个Count。我使用此功能的真正目的是要使用更复杂的过滤器计数,但我什至无法使它正常工作。 sub>
!根据我的修改,正在从我的子查询输出另外一列。这是为了方便订购(COUNT不需要)。
我只需要从模型中删除规定的元顺序。您可以通过在子查询中添加一个空的
1 2 3 | 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)) |
那行得通。太好了很烦人。
也可以创建
1 2 3 | class SQCount(Subquery): template ="(SELECT count(*) FROM (%(subquery)s) _count)" output_field = models.IntegerField() |
然后,您将像使用原始
1 2 | spaces = Space.objects.filter(carpark=OuterRef('pk')).values('pk') Carpark.objects.annotate(space_count=SQCount(spaces)) |
您可以将此技巧(至少在postgres中)与一系列聚合函数结合使用:我经常使用它来构建值数组或求和。
我碰到了一个非常相似的案例,我必须为未取消预订状态的事件预订座位。在尝试解决了几个小时的问题之后,这就是我认为是问题的根本原因:
前言:这是MariaDB,Django 1.11。
注释查询时,它会获得一个带有所选字段的
然后,我开始进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from django.db.models.fields import PositiveIntegerField reserved_seats_qs = SeatReservation.objects.filter( performance=OuterRef(name='pk'), status__in=TAKEN_TYPES ).values('id').annotate( count=Count('id')).values('count') # Query workaround: remove GROUP BY from subquery. Test this # vigorously! reserved_seats_qs.query.group_by = [] performances_qs = Performance.objects.annotate( reserved_seats=Subquery( queryset=reserved_seats_qs, output_field=PositiveIntegerField())) print(performances_qs[0].reserved_seats) |
因此,基本上,您必须手动删除/更新子查询的queryset上的
我相信这是Django错误,或者子查询效率低下。我将为此创建一个错误报告。
编辑:错误报告在这里。
可以使用Django 2.0中的
通过基于外部查询模型(在GROUP BY子句中)计算分区上的聚合,然后将数据注释到子查询queryset的每一行中,这允许注释值的聚合。然后,子查询可以使用返回的第一行中的聚合数据,而忽略其他行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Performance.objects.annotate( reserved_seats=Subquery( SeatReservation.objects.filter( performance=OuterRef(name='pk'), status__in=TAKEN_TYPES, ).annotate( reserved_seat_count=Window( expression=Count('pk'), partition_by=[F('performance')] ), ).values('reserved_seat_count')[:1], output_field=FloatField() ) ) |
如果我理解正确,您正在尝试计算
1 | Carpark.objects.annotate(Count('spaces')) |
结果中将包含一个
好,我看过你的纸条...
我还可以使用我手头的其他模型来运行相同的查询。结果是相同的,因此示例中的查询似乎还可以(使用Django 1.11b1测试):
1 2 3 | activities = Activity.objects.filter(event=OuterRef('pk')).values('event') count_activities = activities.annotate(c=Count('*')).values('c') Event.objects.annotate(spaces__count=Subquery(count_activities)) |
也许您的"最简单的现实世界示例"太简单了……您可以共享模型或其他信息吗?
"为我工作"并没有太大帮助。但。
我在一些方便的型号(
您确定您在正确版本的Django中运行此代码吗?这是您正在运行的实际代码吗?您是否实际上不是在
也许尝试
1 2 3 4 | SELECT (SELECT COUNT(U0."id") AS"c" FROM"carparks_spaces" U0 WHERE U0."carpark_id" = ("carparks_carpark"."id") GROUP BY U0."carpark_id") AS"space_count" FROM"carparks_carpark" |
并非真正的答案,但希望能有所帮助。