【发布时间】:2018-06-15 14:05:58
【问题描述】:
我在添加到现有项目的新应用程序中遇到了一个有趣的情况。我的目标是(使用 Celery 任务)使用包含来自外键对象的注释聚合值的值一次更新多行。以下是我在之前的问题中使用的一些示例模型:
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_save信号更新相关UserBookRead对象上的pages_read。 - 在信号结束时,启动后台Celery任务,从每个更新的
UserBookRead中汇总pages_read,并更新每个相关UserBookStats上的total_pages_read(这就是问题所在)
我正在尝试尽可能精简查询数量 - 步骤 1 已完成,并且只需要针对我的实际用例进行一些查询,这对于信号处理程序来说似乎是可以接受的,只要这些查询是适当优化。
第 2 步涉及更多,因此委托给后台任务。我已经设法以相当干净的方式完成了大部分工作(至少对我而言)。
我遇到的问题是,当使用total_pages 聚合注释UserBookStats 查询集(所有pages_read 中的Sum() 用于相关的UserBookRead 对象)时,我无法直接使用查询集的update 设置total_pages_read 字段。
这是代码(Book 实例以book 的形式传递给任务):
# use the provided book instance to get the stats which need to be updated
book_read_objects= UserBookRead.objects.filter(book=book)
book_stat_objects = UserBookStats.objects.filter(id__in=book_read_objects.values_list('user_book_stats__id', flat=True).distinct())
# annotate top level stats objects with summed page count
book_stat_objects = book_stat_objects.annotate(total_pages=Sum(F('user_book_read__pages_read')))
# update the objects with that sum
book_stat_objects.update(total_pages_read=F('total_pages'))
在执行最后一行时,抛出此错误:
django.core.exceptions.FieldError: Aggregate functions are not allowed in this query
经过一番研究,我发现了此用例 here 的现有 Django 票证,其中最后一条评论提到了 1.11 中的 2 个新功能,这可能使之成为可能。
是否有任何已知/可接受的方法来完成此用例,可能使用Subquery 或OuterRef?我没有成功尝试将聚合折叠为Subquery。这里的后备是:
for obj in book_stat_objects:
obj.total_pages_read = obj.total_pages
obj.save()
但是book_stat_objects 中可能有数万条记录,我真的在努力避免单独为每条记录发出更新。
【问题讨论】:
标签: django django-orm