Django:在queryset update()中使用带注释的聚合
我在添加到现有项目的新应用程序中遇到了一个有趣的情况。我的目标是(使用芹菜任务)使用一个值一次更新多行,该值包括来自外部键控对象的带注释的聚合值。以下是我在前面的问题中使用的一些示例模型:Django:在queryset update()中使用带注释的聚合,django,django-orm,Django,Django Orm,我在添加到现有项目的新应用程序中遇到了一个有趣的情况。我的目标是(使用芹菜任务)使用一个值一次更新多行,该值包括来自外部键控对象的带注释的聚合值。以下是我在前面的问题中使用的一些示例模型: class Book(models.model): author = models.CharField() num_pages = models.IntegerField() num_chapters = models.IntegerField() class UserBookRea
class Book(models.model):
author = models.CharField()
num_pages = models.IntegerField()
num_chapters = models.IntegerField()
class UserBookRead(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
user_book_stats = models.ForeignKey(UserBookStats)
book = models.ForeignKey(Book)
complete = models.BooleanField(default=False)
pages_read = models.IntegerField()
class UserBookStats(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
total_pages_read = models.IntegerField()
我正试图:
Book
页面计数时,使用Book
实例中的post\u save
信号更新相关UserBookRead
对象上的pages\u read
UserBookRead
卷取页面,并更新每个相关UserBookStats
上的总页面(这就是问题发生的地方)
total\u pages
聚合注释UserBookStats
queryset时(对于相关userbooksread
对象,所有pages的Sum()
),我不能直接用queryset的更新来设置总页数\u read
字段
下面是代码(将Book
实例作为Book
传递给任务):
在执行最后一行时,将抛出此错误:
django.core.exceptions.FieldError: Aggregate functions are not allowed in this query
经过一些研究,我找到了这个用例现有的Django标签,最后一条评论提到了1.11中的两个新特性,这可能使它成为可能
是否有任何已知/公认的方法来完成此用例,可能使用子查询
或OuterRef
?我尝试将聚合折叠为子查询
,但没有成功。这里的退路是:
for obj in book_stat_objects:
obj.total_pages_read = obj.total_pages
obj.save()
但是由于book\u stat\u objects中可能有成千上万条记录,我真的试图避免对每一条记录单独进行更新。我最终想出了如何使用子查询和OuterRef
来实现这一点,但不得不采取与我最初预期不同的方法
我能够很快地得到一个子查询
,但是当我用它来注释父查询时,我注意到每个注释的值都是子查询的第一个结果-这是当我意识到我需要OuterRef
,因为生成的SQL没有通过父查询中的任何内容来限制子查询
Django文档的一部分非常有用,StackOverflow问题也是如此。这个过程归结起来就是,您必须使用Subquery
来创建聚合,并使用OuterRef
来确保子查询通过父查询限制聚合行。此时,您可以使用聚合值进行注释,并直接在querysetupdate()
中使用它
正如我在问题中提到的,代码示例是由多个部分组成的。我已尝试通过更改使它们适应我的实际用例:
from django.db.models import Subquery, OuterRef
from django.db.models.functions import Coalesce
# create the queryset to use as the subquery, restrict based on the `book_stat_objects` queryset
book_reads = UserBookRead.objects.filter(user_book_stat__in=book_stat_objects, user_book_stats=OuterRef('pk')).values('user_book_stats')
# annotate the future subquery with the aggregation of pages_read from each UserBookRead
total_pages = book_reads.annotate(total=Sum(F('pages_read')))
# annotate each stat object with the subquery total
book_stats = book_stats.annotate(total=Coalesce(Subquery(total_pages), 0))
# update each row with the new total pages count
book_stats.update(total_pages_read=F('total'))
创建一个不能单独使用的查询集感觉很奇怪(尝试评估book\u reads
会因为包含OuterRef
而抛出一个错误),但是一旦检查为book\u stats
生成的最终SQL,这是有意义的
编辑
在找到这个答案一两周后,我最终遇到了这个代码的错误。结果证明,这是由于UserBookRead
模型的默认排序。作为状态,默认的排序
被合并到任何聚合的GROUP BY
子句中,因此我的所有聚合都被关闭。解决方法是在创建基本子查询时,使用空白的order\u by()
清除默认排序:
book_reads = UserBookRead.objects.filter(user_book_stat__in=book_stat_objects, user_book_stats=OuterRef('pk')).values('user_book_stats').order_by()
book_reads = UserBookRead.objects.filter(user_book_stat__in=book_stat_objects, user_book_stats=OuterRef('pk')).values('user_book_stats').order_by()