Search code examples
pythondjangodjango-modelsdjango-aggregation

Sum together list of F expressions


Is there a way to specify (in an annotation or aggregation) that a sequence of F expressions should be summed together without manually typing out F("first_prop") + F("second_prop") + ...?

I want something similar to how python's sum() function allows you to pass an iterable and get the sum of the values in the iterable i.e. sum([1,2,3]) returns 6.

Concretely, I want something that looks like this:

class Tree(TimeStampedModel):
  leaf_count = models.IntegerField()
  branch_count = models.IntegerField()

Tree.objects.create(leaf_count=60, branch_count=8)
Tree.objects.create(leaf_count=30, branch_count=3)

# now I want to annotate a combined count using my imaginary IterableSum aggregator
combined_sums = list(
  Tree.objects.all().annotate(
    combined_count=IterableSum(fields=[F("leaf_count"), F("branch_count")])
  ).values_list("combined_count", flat=True)
)

combined_sums # [68, 33]

How can I achieve this?


Solution

  • The only problem with sum is that it starts with 0 as initial value. You can you can use reduce from functools:

    from functools import reduce
    from operator import add
    
    from django.db.models import F
    
    combined_sums = list(
        Tree.objects.values(
            combined_count=reduce(add, [F('leaf_count'), F('branch_count')]),
            flat=True,
        )
    )

    although strictly speaking, that is not even necessary, you can just use sum, since it will add up 0 with F('leaf_count'):

    from django.db.models import F
    
    combined_sums = list(
        Tree.objects.values(
            combined_count=sum([F('leaf_count'), F('branch_count')]),
            flat=True,
        )
    )

    Then this will have a + 0 in the query, which might not be ideal.

    This is because sum does not sum only integers, indeed, you can for example sum F objects:

    In [11]: sum([F('foo'), F('bar')])
    Out[11]: <CombinedExpression: Value(0) + F(foo) + F(bar)>