简体   繁体   中英

Django: Annotating Sum() of two columns in different tables

Assuming I have the following models - How can I annotate the total posting and total story reach of each influencer in my queryset?

class Influencer(models.Model):
    name = models.CharField(max_length=100)

class Posting(models.Model):
    influencer = models.ForeignKey(Influencer, on_delete=models.CASCADE)
    reach = models.IntegerField()

class Story(models.Model):
    influencer = models.ForeignKey(Influencer, on_delete=models.CASCADE)
    reach = models.IntegerField()

I have tried this:

queryset = Influencer.objects.all()
queryset = queryset.annotate(posting_reach=Sum("posting__reach"))
queryset = queryset.annotate(story_reach=Sum("story__reach"))

However, the values are not calculated correctly using this approach (I assume because of the LEFT OUTER JOIN which is made by Sum() ). How would I do this in Django?

This will indeed not work, because your query will make two LEFT OUTER JOIN s:

SELECT influencer.*, SUM(posting.reach), SUM(story.reach)
FROM influencer
LEFT OUTER JOIN posting ON posting.influencer_id = influencer.id
LEFT OUTER JOIN story ON story.influencer_id = influencer.id

the two LEFT OUTER JOIN s will thus act as a multiplier of each other, and thus the sum of posting.reach will be multiplied by the number of related story s, and vice versa.

A trick could be to divide it by the number of items of the other relation, so:

from django.db.models import Count, Sum, Value
from django.db.models.functions import Coalesce

queryset = Influencer.objects.annotate(
    posting_reach=Sum('posting__reach'),
    story_reach=Sum('story__reach')
)

For anyone interested in a subquery solution:

from django.db.models import (OuterRef, Subquery, Sum, IntegerField)

subs = {
    'posting_reach': Posting.objects.filter(influencer=OuterRef('pk')),
    'story_reach': Story.objects.filter(influencer=OuterRef('pk')),
}
qs = Influencer.objects.annotate(
    **{key: Subquery(
            sub_q.annotate(sum=Sum('reach')).values('sum'),
            output_field=IntegerField()
        ) for key, sub_q in subs
    }
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM