【问题标题】:Annotating a queryset using aggregations of values with more than one field使用具有多个字段的值聚合来注释查询集
【发布时间】:2021-11-03 14:22:51
【问题描述】:

Django 注释真的很棒。但是我不知道如何处理需要多个 values() 的注释。

问题:

我想用相关 m2m 中的项目数注释 author_queryset。我不知道我是否需要使用Subquery,但是:

annotated_queryset = author_queryset.annotate(genre_counts=Subquery(genre_counts))

返回:

SyntaxError: subquery must return only one column

我已尝试将值转换为 JSONField 以将其返回到一个字段中,希望我可以在其上使用 JSONBagg,因为我使用的是 postgres 并且需要过滤结果:

subquery = Author.objects.filter(id=OuterRef('pk')).values('id','main_books__genre').annotate(genre_counts=Count('main_books__genre'))

qss = qs.annotate(genre_counts=Subquery(Cast(subquery,JSONField()), output_field=JSONField()))

产量:

AttributeError: 'Cast' object has no attribute 'clone'

我不确定需要什么才能将 dict 转换为 JSONField()。有一些很棒的info here 关于过滤这些。在名为ArraySubQuery() 的开发版本中,postgres 也即将推出一些东西,旨在解决这个问题。但是,在它处于稳定版本之前,我无法使用此功能。

想要的结果

我想进行注释,以便可以根据注释进行过滤,如下所示:

annotated_queryset.filter(genre_counts__scifi__gte=5)

详情

我可以使用 dunders 来获取相关领域,然后像这样计算:

# get all the authors with Virginia in their name
author_queryset = Author.objects.filter(name__icontains='Virginia')
author_queryset.count()
# returns: 21

# aggregate the book counts by genre in the Book m2m model
genre_counts = author_queryset.values('id','main_books__genre').annotate(genre_counts=Count('main_books__genre'))
genre_counts.count()

# returns: 25

这是因为查询集中的每个 Author 对象可以返回多个类型计数。在这个特定的示例中,有一个作者拥有 4 种不同类型的书籍:

举例说明:

...
{'id': 'authorid:0054f04', 'main_books__genre': 'scifi', 'genre_counts': 1}
{'id': 'authorid:c245457', 'main_books__genre': 'fantasy', 'genre_counts': 4}
{'id': 'authorid:a129a73', 'main_books__genre': None, 'genre_counts': 0}
{'id': 'authorid:f41f14b', 'main_books__genre': 'mystery', 'genre_counts': 16}
{'id': 'authorid:f41f14b', 'main_books__genre': 'romance', 'genre_counts': 1}
{'id': 'authorid:f41f14b', 'main_books__genre': 'scifi', 'genre_counts': 9}
{'id': 'authorid:f41f14b', 'main_books__genre': 'fantasy', 'genre_counts': 3}
...

还有一个作者有 2 个,其余的每个都有一个流派。这是 25 个值。

希望这对某人有意义!我确信有一种方法可以正确处理此问题,而无需等待上述功能。

【问题讨论】:

    标签: python django postgresql django-models django-queryset


    【解决方案1】:

    您想在没有Subquery 的情况下使用.annotate(,因为您发现它需要返回单个值。您应该能够跨越第一个注释的计数表达式中的所有关系。

    不幸的是,Django 目前不支持您使用genre_counts__scifi_gt=5 查找的内容。您可以构造它,以便使用传递给它的过滤器进行计数。

    selected_genre = 'scifi'
    annotated_queryset = author_queryset.annotate(
        genre_count=Count("main_books__genre", filter=Q(genre=selected_genre))
    ).filter(genre_count__gte=5)
    

    要获得完整的细分,您最好返回细分并在应用程序中进行最终聚合,如您在问题中所示。

    【讨论】:

    • 对!所以要注释整个查询集,例如,可以对每种类型运行一个 for 循环,对每个类型进行注释吗?为什么我不能将多个值的结果转换为JSONField()
    • 我在我的问题中向JSONField() 添加了更多关于演员阵容的细节
    • 我会使用字典理解来创建一个字典,该字典可以通过 .annotate(**genre_counts) 一次调用来解构。
    • Cast 不是可用于子查询的表达式。你可能需要创建自己的表达式类来做你想做的事。
    • 我会小心你产生多少计数。该查询可能会很快变得笨拙。 UX 方面,如果可能的话,我会尝试一次做一个。
    猜你喜欢
    • 1970-01-01
    • 2016-03-16
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 2020-03-07
    • 2014-12-22
    • 1970-01-01
    • 2010-09-14
    相关资源
    最近更新 更多