This is a suggestion to make it easier to annotate using count and suqueries.
Considering this example with three models, where I want to annotate a queryset with the count of related objects in the other two models.
class Discussion(models.Model):
title = models.CharField(max_length=200)
...
class Comment(models.Model):
discussion = models.ForeignKey(Discussion, models.PROTECT)
...
class Reaction(models.Model):
discussion = models.ForeignKey(Discussion, models.PROTECT)
...
Now, if I annotate this queryset using models.Count, I will get table joins at the database level, resulting in a potentially huge response from the server. This seems to happen even if I add .distinct().
Discussion.objects.annotate(comment_count=models.Count('comment'), reaction_count=models.Count('reaction'))
Let’s say I evaluate this on 100 discussions, with each discussion having on average 1000 comments and as many reactions. That makes my database (I’m using psql) return 100x1000x1000 rows. Using this with .distinct avoids loading all rows as objects in Django, but still generates those rows in db, possibly causing an error.
To solve this, I’ve done this little dance:
Discussion.objects.annotate(
comment_count=Comment.objects
.filter(discussion=models.OuterRef("id"))
.annotate(count=models.Func(models.F("id"), function="Count"))
.values("count")
)
This works like a charm, but it’s quite a mouthful! Now, if I try this, I get an error:
Discussion.objects.annotate(
comment_count=Comment.objects
.filter(discussion=models.OuterRef("id"))
.count()
)
>>> ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
What if we made that work, so that QuerySet.count() returned a queryset that can be used for annotations, much like I did above, when called on a QuerySet using OuterRef?
- This would provide a simpler method to avoid row bombs from using models.Count.
- It does not interfere with existing codebases, because doing that currently causes an exception.
- It could probably be implemented in a way that is userful for more types of annotations(?).
Am I missing something? Is there already a better way of achieving this?