使用Django查询多对多(M2M)关系时如何获取单列表数据
Django多对多关系查询:将关联字段聚合为列表
问题说明
此问题对应Stack Overflow经典问题《Django query — how to get list of dictionaries with M2M relation?》,旨在了解近11年Django是否推出了更便捷的功能,实现将多对多关联字段聚合为列表格式返回。
模型定义
class RandomObject(models.Model): name = models.CharField(max_length=50, blank=False) description = models.TextField(blank=False) features = models.ManyToManyField('Features', blank=False, db_table='RandomObjectFeatures') class Features(models.Model): LIGHTING = "LIT" SECURE = "SGE" COVERED = "CVP" FEATURE_NAME_CHOICES = [ (LIGHTING, "Lighting"), (SECURE, "Secured Well"), (COVERED, "Covered"), ] feature_name = models.CharField(max_length=3, choices=FEATURE_NAME_CHOICES, blank=False, null=True) def __str__(self): for code, display_name in self.FEATURE_NAME_CHOICES: if self.feature_name == code: return display_name return self.feature_name
当前查询问题
执行以下查询:
random_objects = RandomObject.objects.all() random_objects_data = random_objects.values('name','features__feature_name')
返回结果为每条特征对应一条数据:
[ {'name': 'object1', 'features__feature_name': 'SGE'}, {'name': 'object1', 'features__feature_name': 'LIT'}, {'name': 'object1', 'features__feature_name': 'CVP'} ]
无法满足需求,期望得到聚合后的格式:
[ {'name': 'object1', 'features__feature_name': ['SGE','LIT','CVP']} ]
解决方案(Django 1.11+ 新增支持)
1. PostgreSQL 专属:使用 ArrayAgg
Django 1.11 开始集成了PostgreSQL的数组聚合函数,可直接将关联字段聚合为列表:
from django.contrib.postgres.aggregates import ArrayAgg from django.db.models import F random_objects_data = RandomObject.objects.annotate( features__feature_name=ArrayAgg(F('features__feature_name')) ).values('name', 'features__feature_name')
执行后直接返回包含特征列表的字典,无需额外处理。
2. MySQL 专属:自定义聚合函数实现分组拼接
MySQL可通过自定义GROUP_CONCAT聚合函数实现,返回字符串后可转为列表:
from django.db.models import Func class GroupConcat(Func): function = 'GROUP_CONCAT' template = "%(function)s(%(expressions)s SEPARATOR ',')" random_objects_data = RandomObject.objects.annotate( features_str=GroupConcat('features__feature_name') ).values('name', 'features_str') # 将字符串转为列表 for item in random_objects_data: item['features__feature_name'] = item.pop('features_str').split(',')
3. 跨数据库通用方案:预加载关联数据后手动构造
无需依赖数据库特定功能,通过prefetch_related提前加载关联数据,再手动组装成目标格式:
random_objects = RandomObject.objects.all().prefetch_related('features') random_objects_data = [] for obj in random_objects: features_list = [feat.feature_name for feat in obj.features.all()] random_objects_data.append({ 'name': obj.name, 'features__feature_name': features_list })
这些方法相比11年前的手动分组逻辑更简洁,是Django后续版本针对此类需求推出的优化方案。
内容的提问来源于stack exchange,提问作者Muffin
相关产品推荐
相关产品推荐

