PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Calculer Max of Sum d'un champ annoté sur une requête groupée par dans Django ORM ?

Vous ne pouvez pas faire un agrégat d'un agrégat Max(Sum()) , il n'est pas valide en SQL, que vous utilisiez l'ORM ou non. Au lieu de cela, vous devez joindre la table à elle-même pour trouver le maximum. Vous pouvez le faire en utilisant une sous-requête. Le code ci-dessous me semble correct, mais gardez à l'esprit que je n'ai pas quelque chose pour l'exécuter, donc ce n'est peut-être pas parfait.

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))

Cela devrait générer SQL comme :

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

Il peut être plus performant d'éliminer la jointure dans Subquery en utilisant une fonctionnalité spécifique au backend comme array_agg (Postgres) ou GroupConcat (MySQL) pour collecter les Relation.ids qui sont regroupés dans la requête externe. Mais je ne sais pas quel backend vous utilisez.