Reputation: 937
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?
Upvotes: 1
Views: 1717
Reputation: 16666
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
}
)
Upvotes: 0
Reputation: 476554
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') / Coalesce(Count('story'), Value(1)),
story_reach=Sum('story__reach') / Coalesce(Count('posting'), Value(1))
)
Upvotes: 3