django Count aggregate is not working as I intended

Question:

It’s a code that reproduces the problem I experienced.

models.py

from django.db import models


class User(models.Model):
    pass


class Group(models.Model):
    users = models.ManyToManyField(
        User,
        related_name='groups',
    )


class Notice(models.Model):
    groups = models.ManyToManyField(
        Group,
        related_name='notices',
    )

tests.py

from django.test import TestCase
from tests.models import User, Group, Notice
from django.db.models.aggregates import Count


def print_user_count(group):
    count = group.users.count()
    annotate_count = (
        Group.objects
        .annotate(
            user_count=Count('users'),
            notice_count=Count('notices')
        )
        .get(id=group.id)
        .user_count
    )
    print('count: %d, annotate_count: %d' % (count, annotate_count))


class ModelTestCase(TestCase):
    def test_(self):
        user1 = User.objects.create()
        group1 = Group.objects.create()
        group1.users.set([user1])

        print_user_count(group1) # count: 1, annotate_count: 1
        for _ in range(5):
            notice = Notice.objects.create()
            notice.groups.set([group1])
        print_user_count(group1) # count: 1, annotate_count: 5

I didn’t add users to the group.
But the value obtained using annotate has increased from 1 to 5.

Is this a bug? Or did I use something wrong?

Asked By: gypark

||

Answers:

This is not a problem, actually this is explained nicely in the documentation:

Combining multiple aggregations with annotate() will yield the wrong results because joins are used instead of subqueries:

documentation

Possible solution:

  1. Use Count('users', distinct=True).
  2. Use subquery.
Answered By: Rohit Rahman