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

DRF如何按模型id字段分组序列化数据并聚合客户端ID

DRF按Mailing分组聚合Client ID数组的最优方案

直接用数据库层面的聚合函数实现,彻底避免SerializerMethodField带来的N+1查询问题,分数据库类型给出具体实现:

一、PostgreSQL环境(推荐,原生支持数组聚合)

1. 调整查询集(views.py)

导入PostgreSQL专属的ArrayAgg,在annotate中聚合关联的Client ID:

from django.contrib.postgres.aggregates import ArrayAgg
from .models import Mailing
from rest_framework import generics

class MailingListView(generics.ListAPIView):
    serializer_class = MailingSerializer

    def get_queryset(self):
        # 按Mailing的id分组,聚合去重后的Client ID数组
        return Mailing.objects.annotate(
            client_ids=ArrayAgg('client__id', distinct=True)  # 注意:'client__id'需匹配你的模型关联字段,比如多对多可能是'clients__id'
        ).values('id', 'client_ids', '其他需要的字段')  # 只查询必要字段,提升性能

2. 修改序列化器(serializers.py)

直接映射聚合后的client_ids字段为数组:

from rest_framework import serializers
from .models import Mailing

class MailingSerializer(serializers.ModelSerializer):
    clients = serializers.ListField(child=serializers.IntegerField(), source='client_ids')

    class Meta:
        model = Mailing
        fields = ['id', 'clients', '其他需要的字段']

二、MySQL环境(需自定义聚合函数)

MySQL原生无ArrayAgg,用GROUP_CONCAT实现字符串聚合后转数组:

1. 自定义聚合函数(可放在models.py或单独的utils文件)

from django.db.models import Func
from django.db.models.fields import CharField

class GroupConcat(Func):
    function = 'GROUP_CONCAT'
    template = "%(function)s(%(expressions)s SEPARATOR ',')"
    output_field = CharField()

2. 调整查询集(views.py)

from .models import Mailing
from .utils import GroupConcat  # 或自定义函数的实际路径
from rest_framework import generics

class MailingListView(generics.ListAPIView):
    serializer_class = MailingSerializer

    def get_queryset(self):
        return Mailing.objects.annotate(
            client_ids_str=GroupConcat('client__id', distinct=True)
        ).values('id', 'client_ids_str', '其他需要的字段')

3. 修改序列化器(serializers.py)

将聚合得到的字符串转为整数数组:

from rest_framework import serializers
from .models import Mailing

class MailingSerializer(serializers.ModelSerializer):
    clients = serializers.SerializerMethodField()

    def get_clients(self, obj):
        # 处理空值情况,避免报错
        client_str = obj.get('client_ids_str')
        return list(map(int, client_str.split(','))) if client_str else []

    class Meta:
        model = Mailing
        fields = ['id', 'clients', '其他需要的字段']

三、排查之前ArrayAgg失败的常见原因

  • 数据库不是PostgreSQL:MySQL不支持ArrayAgg,需用上述MySQL方案
  • 关联字段路径错误:比如模型中Mailing与Client是多对多,关联字段名为clients,则应写clients__id而非client__id
  • 未加distinct=True:如果存在重复关联,会导致数组中出现重复ID

效果验证

之前的输出(单条Client ID):

[
  {"id": 1, "client": 123},
  {"id": 1, "client": 456}
]

优化后的期望输出:

[
  {"id": 1, "clients": [123, 456]}
]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:56:19