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
相关产品推荐
相关产品推荐

