计算Django ORM中按分组分组的带注释字段的最大总和?

时间:2018-01-12 13:08:29

标签: python django postgresql orm aggregate

为了保持简单,我有四个表(A,B,类别和关系),关系表在B中存储A的f :: forall r. (Bool -> ListScott Bool -> r) -> r -> r,而类别存储B的类型。

  

A< --- Relation ---> B --->分类

(所以A和B之间的关系是n到n,当B和Category之间的关系是n到1时)

我需要ORM按类别和A对关系记录进行分组,然后在每个(类别,A)中计算Intensity的{​​{1}}(看起来很简单直到这里),然后我想要注释Max of在每个类别中计算Sum

我的代码类似于:

Intensity

引发错误:

Sum

Django-group-by包含相同错误。

有关详细信息,请参阅this stackoverflow question

我正在使用Django 2和PostgreSQL。

有没有办法使用ORM实现这一点,如果没有,使用原始SQL表达式的解决方案是什么?

更新

经过多次努力,我发现我写的确实是一个聚合,但我想要的是找出每个类别中每个A的最大AcSum。所以我想我必须在AcSum计算后再次按结果分组。基于这种见解,我发现stack-overflow question提出了同样的概念(问题是在1年,2个月前被问到没有任何接受的答案)。 将另一个值(' id')链接到该集既不作为group_by也不作为输出属性的过滤器起作用,它从集合中删除AcSum。由于按结果集分组的更改,因此也无法将值添加到值()中。 我想我要做的是根据列内的字段(即id)重新分组查询。 有什么想法吗?

2 个答案:

答案 0 :(得分:1)

无论是否使用ORM,都无法对聚合Max(Sum())进行聚合,它在SQL中无效。相反,您必须将表连接到自身才能找到最大值。您可以使用子查询执行此操作。下面的代码对我来说是正确的,但请记住,我没有什么可以运行它,所以它可能不完美。

from django.db.models import Subquery, OuterRef

annotation = {
    'AcSum': Sum('intensity')
}
# The basic query is on Relation grouped by A and Category, annotated
# with the Sum of intensity
query = Relation.objects.values('a', 'b__category').annotate(**annotation)

# The subquery is joined to the outerquery on the Category
sub_filter = Q(b__category=OuterRef('b__category'))
# The subquery is grouped by A and Category and annotated with the Sum
# of intensity, which is then ordered descending so that when a LIMIT 1
# is applied, you get the Max.
subquery = Relation.objects.filter(sub_filter).values('a', 'b__category').annotate(**annotation).order_by('-AcSum').values('AcSum')[:1]

query = query.annotate(max_intensity=Subquery(subquery))

这应该生成如下的SQL:

SELECT a_id, category_id,
       (SELECT SUM(U0.intensity) AS AcSum
        FROM RELATION U0
        JOIN B U1 on U0.b_id = U1.id
        WHERE U1.category_id = B.category_id
        GROUP BY U0.a_id, U1.category_id
        ORDER BY SUM(U0.intensity) DESC
        LIMIT 1
       ) AS max_intensity
FROM Relation
JOIN B on Relation.b_id = B.id
GROUP BY Relation.a_id, B.category_id

通过使用诸如array_agg(Postgres)或GroupConcat(MySQL)之类的后端特定功能来收集在外部查询中组合在一起的Relation.ids,消除子查询中的连接可能更为高效。但我不知道你正在使用什么后端。

答案 1 :(得分:0)

这样的事情对你有用。我自己无法测试,所以请让我知道结果:

Relation.objects.annotate(
   b_category=F('B__Category')
).values(
   'A', 'b_category'
).annotate(
   SumInensityPerCategory=Sum('Intensity')
).values(
   'A', MaxIntensitySumPerCategory=Max('SumInensityPerCategory')
)