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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:14:58