【问题标题】:Calculate Max of Sum of an annotated field over a grouped by query in Django ORM?计算Django ORM中按查询分组的注释字段的最大总和?
【发布时间】:2018-06-21 22:57:02
【问题描述】:

为了简单起见,我有四个表(A、B、Category 和 Relation),Relation 表将 A 的 Intensity 存储在 B 中,Category 存储 B 的类型。

A B ---> 类别

(所以A和B的关系是n比n,当B和Category的关系是n比1)

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

我的代码是这样的:

 A.objects.values('B_id').annotate(AcSum=Sum(Intensity)).annotate(Max(AcSum))

哪个会引发错误:

django.core.exceptions.FieldError: Cannot compute Max('AcSum'): 'AcSum' is an aggregate

Django-group-by 包同样的错误。

更多信息请参见this stackoverflow question

我正在使用 Django 2 和 PostgreSQL。

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

更新

经过一番挣扎,我发现我写的确实是一个聚合,但是我想要找出每个类别中每个 A 的最大 AcSum。所以我想我必须在 AcSum Calculation 之后再次对结果进行分组。基于这种见解,我发现了一个stack-overflow question,它提出了相同的概念(这个问题是在 1 年 2 个月前提出的,没有任何公认的答案)。 将另一个值('id')链接到集合既不能用作 group_by,也不能用作输出属性的过滤器,它会从集合中删除 AcSum。由于按结果集分组的变化,将 AcSum 添加到 values() 也不是一个选项。 我想我想要做的是根据列内的字段(即 id)重新分组查询分组。 有什么想法吗?

【问题讨论】:

    标签: python django postgresql orm aggregate


    【解决方案1】:

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

    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,消除Subquery 中的连接可能会更高效.但我不知道你用的是什么后端。

    【讨论】:

    • 感谢您的宝贵时间和洞察力。你让我今天一整天都感觉很好! @布拉德·马茨伯格
    • 您将在六个小时内领奖。
    【解决方案2】:

    这样的东西应该适合你。我自己无法测试,所以请告诉我结果:

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

    【讨论】:

    • 谢谢,但是我得到了同样的错误:django.core.exceptions.FieldError: Cannot compute Max('SumInensityPerCategory'): 'SumInensityPerCategory' is a aggregate @Ahmad
    猜你喜欢
    • 2020-01-26
    • 2019-03-26
    • 2016-06-22
    • 1970-01-01
    • 1970-01-01
    • 2013-03-25
    • 1970-01-01
    • 2020-08-21
    • 1970-01-01
    相关资源
    最近更新 更多