可以单独对多个列进行 GROUP BY 并使用 django ORM 将它们中的每一列聚合到其他列?

Posted

技术标签:

【中文标题】可以单独对多个列进行 GROUP BY 并使用 django ORM 将它们中的每一列聚合到其他列?【英文标题】:It is possible to GROUP BY multiple columns separately and aggregate each one of them by other column with django ORM? 【发布时间】:2021-10-15 09:00:44 【问题描述】:

我知道如何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')]>

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

>>> 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')]>

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

预期结果:

<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')]>

理想情况下,它可以拆分为:

<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')]]>

但这只是一个很大的优势。

【问题讨论】:

将多个字段链接到值没有帮助?例如:values('is_fixed', 'source', 'category'). 不,因为它会将所有字段组合在一起:'is_fixed': False, 'category': 'FOOD', 'source': 'SETTLE_UP', 'total': Decimal('33.9000000000000') 您的预期结果是什么? @Crash0v3rrid3 我只是更新问题。 并不是在Django中做不到。我看不出有一种方法可以在原始 SQL 中实现这一点。单个查询允许您按一个或多个列分组,但即使您按多列分组,您也只需告诉数据库将在这些列中具有相同值的所有行放入一个组中。您唯一的选择是将其拆分为三个查询。 【参考方案1】:

答案是否定的,因为使用 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"))

现在结果将如下所示:

<ExpenseQueryset ['category': 'FOOD', 'is_fixed': False, 'source': 'MONEY', 'total': Decimal('33.9000000000000'),  ..., 

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

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)

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

['category': 'FOOD', 'total': 33.3, ... other category results ...

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

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)

全球解决方案

现在我们知道如何使用这个解决方案了,这里有一个函数可以给你想要的字段,并且会动态地为你生成正确的结果。

def find_group_by_separated_by_keys(key_list):
    """ key_list in this example will be:
        key_list = ['category', 'source', 'is_fixed']
    """
    qs = Expense.objects.order_by().values(*tuple(key_list)).annotate(total=Sum("price"))
    qs = list(qs)
    result = []
    for key in key_list:
        key_values = []
        for item in qs:
            if not item[key] in key_values:
                key_values.append(item[key])
        
        key_result = []
        for v in key_values:
            value = sum(item['total'] for item in qs if item[key] == v)
            key_result.append(key: v, 'total': value)

        result.extend(key_result)
    return result

现在只需在你的代码中像下面这样简单地使用它:

find_group_by_separated_by_keys(['category', 'source', 'is_fixed')

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

【讨论】:

这是一个解决方案,但我只是想知道它是否值得:我只有一个查询,但我得到了 4*len(key_list) 循环 + 更高的代码复杂性。无论如何,谢谢! 4*len(key_list) 不会造成太大的麻烦。导致问题的唯一情况是key_list 就像百万选择并且有许多价值选择。并且代码复杂性不是问题,因为我提供了一个现成的功能。 代码复杂度不在于“即插即忘”某些代码的可能性,还在于谁来维护它等等...... 请问哪一部分比较复杂,也许可以写得更好。而且,如果这种方法对您不起作用,那么应该关闭这个问题,因为它从来没有使用 SQL 的方法以您想要的方式为您提供结果。原因 Django ORM 是基于 SQL 的,这个操作不是用 SQL 处理的。但你也可以 w8 让其他人给出相同的答案。 我更新了答案,因为 SQL 不可能。但很乐意 w8 看看是否有其他人有使用 SQL 的方法。

以上是关于可以单独对多个列进行 GROUP BY 并使用 django ORM 将它们中的每一列聚合到其他列?的主要内容,如果未能解决你的问题,请参考以下文章

SQL中查询多个字段时,GROUP BY 要怎么使用?

对 SQL 中的两个单独列使用 Group By 来计算新列

sql语言 怎么求每组最大,就是用group by 分组后,求每组某列最大?

通过 Group By Pandas 创建两个聚合列

MySQL之分组查询(GROUP BY)

如何在 SQL 中连接多个表和 GROUP BY