Hi all,
I’d like to start a discussion about allowing aggregate expressions over related objects in QuerySet.update().
The problem
Denormalized counters and totals are a common pattern, and refreshing them in bulk currently fails:
python
Author.objects.update(book_count=Count("book"))
# FieldError: Aggregate functions are not allowed in this query
Current workaround
python
from django.db.models import OuterRef, Subquery, Count
counts = (
Book.objects.filter(author=OuterRef("pk"))
.order_by()
.values("author")
.annotate(c=Count("pk"))
.values("c")
)
Author.objects.update(book_count=Subquery(counts))
This works, but it’s verbose and hard to discover. The .order_by() and the values().annotate().values() sequence are easy to get wrong. Missing rows also produce NULL rather than 0, so users often need Coalesce as well.
Proposal for discussion
Allow update() to accept aggregates over related objects and have Django compile them into the equivalent correlated subquery because adding a subquery is like a universal to all databases option. I’m not attached to this specific approach. A documented helper or a docs section on the Subquery pattern could be a lighter alternative.
Open questions
- What should the semantics be? My reading is per-row aggregation over related rows, but that needs to be stated explicitly.
- How should empty sets behave?
Countnaturally gives 0, whileSumandAvggiveNULL. Should Django coalesce, or leave it to the user? - Scope: aggregating over a different table works on all backends via a correlated subquery. Self-referential aggregates (the same table in the subquery) are restricted on MySQL and would need separate handling, so I’d suggest leaving them out of the initial scope.
- How would this interact with the existing restriction on
F()expressions that span joins inupdate()?
Motivation
This came up when I needed to update a column from an aggregate over related rows. The Subquery approach works, but it took me a while to find and get right, which makes me think other users struggle with it too.
If there’s interest, I’m happy to work on a prototype or a docs patch.
Thanks,
Abhay