You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django ORM如何按多对多作者组合GroupBy统计图书总价格

解决方案

针对多对多作者组合分组统计的需求,提供两种实现方案:

方案1:ORM数据库层面聚合(性能更优,适合数据量较大的场景)

核心思路是给每本图书生成唯一的作者组合标识(按作者ID排序后拼接),再基于该标识分组聚合。

代码示例

首先导入依赖:

from django.db import models
from django.db.models import Sum

适配PostgreSQL数据库:

使用StringAgg聚合函数:

from django.db.models import StringAgg

book_group_stats = Book.objects.annotate(
    # 按作者ID升序排序后拼接为字符串,保证同一作者组合生成的标识完全一致
    author_group_key = StringAgg(
        Cast('author__id', output_field=models.CharField()), 
        ',', 
        ordering='author__id ASC'
    )
).values('author_group_key').annotate(
    total_price = Sum('price'),
    # 聚合该分组下所有图书的ID和名称,按需选择
    book_ids = StringAgg(Cast('id', output_field=models.CharField()), ','),
    book_names = StringAgg('name', ',')
)

适配MySQL数据库:

将StringAgg替换为MySQL专属的GroupConcat即可:

from django.db.models import GroupConcat

book_group_stats = Book.objects.annotate(
    author_group_key = GroupConcat(
        Cast('author__id', output_field=models.CharField()), 
        ',', 
        ordering='author__id ASC'
    )
).values('author_group_key').annotate(
    total_price = Sum('price'),
    book_ids = GroupConcat(Cast('id', output_field=models.CharField()), ','),
    book_names = GroupConcat('name', ',')
)

结果解析

遍历查询结果即可获取对应作者组合、图书列表和总价格:

for group in book_group_stats:
    # 解析作者ID列表,查询作者实例
    author_ids = list(map(int, group['author_group_key'].split(',')))
    authors = Author.objects.filter(id__in=author_ids).order_by('id')
    # 解析图书ID列表,查询图书实例
    book_ids = list(map(int, group['book_ids'].split(',')))
    books = Book.objects.filter(id__in=book_ids)
    # 总价格直接取group['total_price']
    total_price = group['total_price']

方案2:Python层面分组(灵活无数据库依赖,适合数据量较小的场景)

如果不想适配不同数据库的聚合函数,也可以全量查询后在Python内存中分组:

from collections import defaultdict
from django.db.models import Prefetch

# 预加载作者避免N+1查询
books = Book.objects.all().prefetch_related(Prefetch('author', queryset=Author.objects.order_by('id')))

group_result = defaultdict(lambda: {'books': [], 'total_price': 0})
for book in books:
    # 用排序后的作者ID元组作为分组key,可哈希
    author_key = tuple(auth.id for auth in book.author.all())
    group_result[author_key]['books'].append(book)
    group_result[author_key]['total_price'] += book.price

# 遍历分组结果
for author_key, data in group_result.items():
    authors = Author.objects.filter(id__in=author_key).order_by('id')
    books = data['books']
    total_price = data['total_price']

注意事项

  • 必须对作者ID排序后生成分组key,避免[A,B]和[B,A]被识别为不同分组
  • MySQL的GroupConcat默认有长度限制,作者数量较多时需要调整数据库参数group_concat_max_len

内容的提问来源于stack exchange,提问作者Sean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 07:42:00