Given a queryset, I add the count of related objects (ModelA) with the following:
qs = User.objets.all()
qs.annotate(modela__count=models.Count(\'modela\'))
In a LEFT JOIN
, every field of modela
could be NULL
because of the absence of corresponding row. So
modela.deleted_at IS NULL
...is not only true for the matching rows, but also true for those users
whose have no corresponding modela
rows.
I think the right SQL should be:
SELECT COUNT(
CASE
WHEN
`modela`.`user_id` IS NOT NULL -- Make sure modela rows exist
AND `modela`.`deleted_at` IS NULL
THEN 1
ELSE NULL
END
) AS `modela__count`,
`users`.*
FROM `users`
LEFT OUTER JOIN `modela`
ON ( `users`.`id` = `modela`.`user_id` )
GROUP BY `users`.`id`
In Django 1.8 this should be:
from django.db import models
qs = User.objects.all().annotate(
modela_count=models.Count(
models.Case(
models.When(
modela__user_id__isnull=False,
modela__deleted_at__isnull=True,
then=1,
)
)
)
)
Notice:
@YAmikep discovered that a bug in Django 1.8.0 makes the generated SQL have an INNER JOIN
instead of a LEFT JOIN
, so you will lose rows without corresponding foreign key relationship. Use Django 1.8.2 or above version to fix that.
In Django 1.8 I believe this can be achieved with conditional aggregation . However for previous versions I would do it with .extra
ModelA.objects.extra(select={
'account_count': 'SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'
})
You can simply filter before you annotate:
from django.db.models import Q, Count
qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))