Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/django/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Django:在queryset update()中使用带注释的聚合_Django_Django Orm - Fatal编程技术网

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
    上的
    总页面(这就是问题发生的地方)
  • 我正在尽可能地减少查询的数量——步骤1已经完成,只需要对我的实际用例进行一些查询,这对于信号处理程序来说似乎是可以接受的,只要这些查询得到了适当的优化

    第2步更为复杂,因此授权给后台任务。我已经设法以一种相当干净的方式完成了大部分工作(至少对我来说是这样)

    我遇到的问题是,当用
    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
    来确保子查询通过父查询限制聚合行。此时,您可以使用聚合值进行注释,并直接在queryset
    update()
    中使用它

    正如我在问题中提到的,代码示例是由多个部分组成的。我已尝试通过更改使它们适应我的实际用例:

    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()