Django DRF查询符合Child筛选条件的Parent时如何附加匹配ChildID且不重复查询数据库
实现方案
完全可以通过Django内置聚合函数配合DRF序列化器实现该需求,全程仅执行1次数据库查询,无额外访问开销,比原有实现的查询效率更高。
前置依赖
- Django 3.2+(PostgreSQL数据库可支持更早版本),如果使用MySQL需Django 4.1+ + MySQL 8.0+
- 无需安装额外第三方依赖
步骤1:改造查询集
使用Django内置的ArrayAgg聚合函数,直接在Parent查询时注解出匹配的Child ID数组,同时完成过滤逻辑:
from django.db.models import ArrayAgg, Q from rest_framework import generics # 其他导入保持不变 class ParentSearchByChildNameView(generics.ListAPIView): """ 返回所有名下存在符合名称搜索条件子节点的父节点列表,附带匹配的子节点ID数组 """ serializer_class = ParentSerializer def get_queryset(self): child_name = self.request.query_params.get('name') queryset = Parent.objects.all() if child_name is not None: queryset = queryset.annotate( # 聚合所有符合名称条件的子节点ID为数组 matched_child=ArrayAgg( 'children__id', filter=Q(children__name__contains=child_name), distinct=True # 避免重复ID,可根据业务需要取舍 ) # 过滤掉没有匹配子节点的父节点,和原有逻辑保持一致 ).filter(matched_child__isnull=False) return queryset
步骤2:修改序列化器
在ParentSerializer中新增对应注解字段的读取配置即可:
from rest_framework import serializers # 导入Parent模型 class ParentSerializer(serializers.ModelSerializer): # 直接读取查询集注解出来的数组字段 matched_child = serializers.ListField(child=serializers.IntegerField(), read_only=True) class Meta: model = Parent fields = ('url', 'name', 'matched_child')
方案说明
该方案的所有逻辑都在单次SQL查询中完成,聚合、过滤操作均在数据库层面执行,不会产生后续的N+1查询或者额外数据库访问,性能远高于先查子节点再遍历填充父节点字段的实现。
内容的提问来源于stack exchange,提问作者keehun
相关产品推荐
相关产品推荐

