是否可以分别对多个列进行 GROUP BY 并使用 django ORM 通过其他列聚合它们中的每一个?

Mur*_*nio 5 python django django-queryset django-aggregation

我知道如何GROUP BY汇总:

>>> from expenses.models import Expense
>>> from django.db.models import Sum
>>> qs = Expense.objects.order_by().values("is_fixed").annotate(is_fixed_total=Sum("price"))
>>> qs
<ExpenseQueryset [{'is_fixed': False, 'is_fixed_total': Decimal('1121.74000000000')}, {'is_fixed': True, 'is_fixed_total': Decimal('813.880000000000')}]>
Run Code Online (Sandbox Code Playgroud)

但是,如果我想对其他两列做同样的事情,它只返回最后一个:

>>> qs = (
...     Expense.objects.order_by()
...     .values("is_fixed")
...     .annotate(is_fixed_total=Sum("price"))
...     .values("source")
...     .annotate(source_total=Sum("price"))
...     .values("category")
...     .annotate(category_total=Sum("price"))
... )
>>> qs
<ExpenseQueryset [{'category': 'FOOD', 'category_total': Decimal('33.9000000000000')}, {'category': 'GIFT', 'category_total': Decimal('628')}, {'category': 'HOUSE', 'category_total': Decimal('813.880000000000')}, {'category': 'OTHER', 'category_total': Decimal('307')}, {'category': 'RECREATION', 'category_total': Decimal('100')}, {'category': 'SUPERMARKET', 'category_total': Decimal('52.8400000000000')}]>
Run Code Online (Sandbox Code Playgroud)

可以只用一个查询而不是三个查询来完成我想要的吗?

预期结果:

<ExpenseQueryset [{'category': 'FOOD', 'total': Decimal('33.9000000000000')}, {... all other categories ...}, 
{'source': 'MONEY', 'total': Decimal('100')}, {... all other sources ...}, {'is_fixed': False, 'total': Decimal('1121.74000000000')}, {'is_fixed': True, 'total': Decimal('813.880000000000')}]>
Run Code Online (Sandbox Code Playgroud)

最理想的情况是,它可以拆分为以下内容:

<ExpenseQueryset ['categories': [{'category': 'FOOD', 'total': Decimal('33.9000000000000')}, {... all other categories ...}], 
'sources': [{'source': 'MONEY', 'total': Decimal('100')}, {... all other sources ...}], 'type': [{'is_fixed': False, 'total': Decimal('1121.74000000000')}, {'is_fixed': True, 'total': Decimal('813.880000000000')}]]>
Run Code Online (Sandbox Code Playgroud)

但这只是一个很大的好处。

Rez*_*adi 2

答案是否定的,因为 SQL 不可能

但是您可以使用以下方法结合 python 编码:

我认为即使在原始 SQL 中也是不可能的,因为在每个查询中您可以按一个或多个字段分组在一起,但不能为每个字段提供单独的结果。 但可以通过一个查询来完成此操作,并使用少量的 Python 代码以您想要的格式合并结果。下面我描述了如何逐步使用它。并在下一节中编写了一个 python 方法,您可以动态地将其用于任何进一步的用途。

怎么运行的

我可以提到的唯一简单的解决方案是,按所需的 3 个字段进行分组,并进行简单的 Python 编程,将每个字段的结果相加。在此方法中,您将只有一个查询,但每个字段分组都有单独的结果。

from expenses.models import Expense
from django.db.models import Sum

qs = Expense.objects.order_by().values("is_fixed", "source", "category").annotate(total=Sum("price"))
Run Code Online (Sandbox Code Playgroud)

现在结果将如下所示:

<ExpenseQueryset [{'category': 'FOOD', 'is_fixed': False, 'source': 'MONEY', 'total': Decimal('33.9000000000000')}, { ...}, 
Run Code Online (Sandbox Code Playgroud)

现在我们可以通过迭代这个结果来简单地聚合每个字段结果

<ExpenseQueryset [{'category': 'FOOD', 'is_fixed': False, 'source': 'MONEY', 'total': Decimal('33.9000000000000')}, { ...}, 
Run Code Online (Sandbox Code Playgroud)

该字段的结果category将是这样的:

[{'category': 'FOOD', 'total': 33.3}, {... other category results ...}
Run Code Online (Sandbox Code Playgroud)

现在我们可以继续按字段为其他分组生成结果is_fixedsource如下所示:

category_keys = []
for q in qs:
    if not q['category'] in category_keys:
        category_keys.append(q['category'])

# Now we have proper values of category in category_keys
category_result = []
for c in category_keys:
    value = sum(item['total'] for item in qs if item['category'] == c)
    category_result.append({'category': c, 'total': value)
Run Code Online (Sandbox Code Playgroud)

全球解决方案

现在我们知道如何使用这个解决方案,这里有一个函数,可以为您提供您想要的字段,并动态地为您提供正确的结果。

[{'category': 'FOOD', 'total': 33.3}, {... other category results ...}
Run Code Online (Sandbox Code Playgroud)

现在只需在您的代码中简单地使用它,如下所示:

source_keys = []
for q in qs:
    if not q['source'] in source_keys:
        source_keys.append(q['source'])
source_result = []
for c in source_keys:
    value = sum(item['total'] for item in qs if item['source'] == c)
    source_result.append({'source': c, 'total': value)

is_fixed_keys = []
for q in qs:
    if not q['is_fixed'] in is_fixed_keys:
        source_keys.append(q['is_fixed'])
is_fixed_result = []
for c in is_fixed_keys:
    value = sum(item['total'] for item in qs if item['is_fixed'] == c)
    is_fixed_result.append({'is_fixed': c, 'total': value)
Run Code Online (Sandbox Code Playgroud)

它会给出一个值列表,例如您想要的正确格式