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

DRF中Annotate字段高效排序的两大实现问题

Django 排序优化与默认降序配置问题解决方案

问题背景

你定义了如下Django模型:

class AAA(models.Model):
    name = models.CharField(max_length=300, null=False, blank=False)

class BBB(models.Model):
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=False, blank=False)
    content = models.CharField(max_length=300, null=False, blank=False)

class CCC(models.Model):
    aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=False, blank=False)
    name = models.CharField(max_length=300, null=False, blank=False)

并在视图中配置了排序与分页:

class AAAListCreateView(generics.ListAPIView):
    class AAAPagination(CursorPagination):
        page_size = 2

    queryset = AAA.objects.all().annotate(
        b_count=Count("bbb", distinct=True),
        c_count=Count("ccc", distinct=True),
        total=F('b_count')+('c_count')
    )
    serializer_class = AAAListSerializer
    pagination_class = AAAPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]
    filterset_class = AAAListFilterSet
    ordering_fields = ['b_count', 'c_count', 'total']
    ordering = 'b_count'

针对你提出的两个问题,解决方案如下:


1. 让所有排序字段默认按降序排列

默认的OrderingFilter需要手动加-前缀实现降序,要实现默认降序,只需自定义一个OrderingFilter子类,重写get_ordering方法,自动为未指定方向的字段添加降序前缀:

from rest_framework.filters import OrderingFilter

class DefaultDescOrderingFilter(OrderingFilter):
    def get_ordering(self, request, queryset, view):
        ordering = super().get_ordering(request, queryset, view)
        if not ordering:
            # 无自定义排序时,把默认排序转成降序
            return [f"-{field}" for field in view.ordering]
        # 处理每个排序字段:无+/-前缀则默认加-
        processed_ordering = []
        for field in ordering:
            if field.startswith(('+', '-')):
                processed_ordering.append(field)
            else:
                processed_ordering.append(f"-{field}")
        return processed_ordering

然后在视图中替换默认的OrderingFilter:

class AAAListCreateView(generics.ListAPIView):
    # ... 保留原有其他代码 ...
    filter_backends = [DjangoFilterBackend, DefaultDescOrderingFilter]
    ordering_fields = ['b_count', 'c_count', 'total']
    ordering = 'b_count'  # 会自动转为 '-b_count' 作为默认排序

这样用户请求时,传?ordering=b_count就会按降序排列,若要升序只需传?ordering=+b_count即可。


2. 优化annotate查询,仅统计当前排序需要的字段

目前提前annotate所有字段会导致每次查询都计算b_count、c_count和total,即使用户只按其中一个字段排序。可以重写视图的get_queryset方法,根据当前请求的排序参数,动态添加需要的annotate字段:

from django.db.models import Count, F
from rest_framework import generics

class AAAListCreateView(generics.ListAPIView):
    class AAAPagination(CursorPagination):
        page_size = 2

    # 仅保留基础queryset,不提前annotate
    queryset = AAA.objects.all()
    serializer_class = AAAListSerializer
    pagination_class = AAAPagination
    filter_backends = [DjangoFilterBackend, DefaultDescOrderingFilter]
    filterset_class = AAAListFilterSet
    ordering_fields = ['b_count', 'c_count', 'total']
    ordering = 'b_count'

    def get_queryset(self):
        queryset = super().get_queryset()
        # 获取当前请求的排序参数,无参数时用默认排序
        ordering = self.request.query_params.get(self.ordering_param, self.ordering)
        # 拆解排序字段,去掉方向前缀
        order_fields = [field.lstrip('+-') for field in ordering.split(',')]
        
        # 根据需要的字段动态添加annotate
        annotate_kwargs = {}
        if 'b_count' in order_fields:
            annotate_kwargs['b_count'] = Count("bbb", distinct=True)
        if 'c_count' in order_fields:
            annotate_kwargs['c_count'] = Count("ccc", distinct=True)
        if 'total' in order_fields:
            # 计算total需要先确保b_count和c_count已被annotate
            if 'b_count' not in annotate_kwargs:
                annotate_kwargs['b_count'] = Count("bbb", distinct=True)
            if 'c_count' not in annotate_kwargs:
                annotate_kwargs['c_count'] = Count("ccc", distinct=True)
            annotate_kwargs['total'] = F('b_count') + F('c_count')
        
        return queryset.annotate(**annotate_kwargs)

这样处理后,查询只会计算当前排序用到的字段,比如用户按total排序时才会计算b_count、c_count和total,按b_count排序时只计算b_count,大幅减少不必要的统计查询,提升效率。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:47:03