Django中如何自定义QuerySet记录排序顺序,替代遍历全量元素的方案?
Django自定义QuerySet记录排序顺序的最优解决方案
以下方案均不需要遍历全量QuerySet,性能远高于Python层遍历重置排序的实现:
场景1:仅临时调整单次查询排序(不修改数据库存储数据)
通过Django ORM的annotate结合Case/When条件表达式,直接在数据库层面生成自定义排序字段,全程不需要把数据加载到Python内存处理:
from django.db.models import Case, When, IntegerField target_id = 5 # 目标位置为第3位(从1开始计数) target_pos = 3 sorted_qs = YourModel.objects.annotate( custom_sort=Case( # 非目标记录中原本排在前2位的优先排序 When(id__ne=target_id, order_number__lt=target_pos, then=0), # 目标记录排在第二优先级 When(id=target_id, then=1), # 剩余非目标记录排在最后 default=2, output_field=IntegerField() ) # 先按自定义优先级排序,同一优先级内按原有order_number升序排列 ).order_by('custom_sort', 'order_number')
执行后得到的就是你预期的排序结果,性能不受数据总量影响。
场景2:需要持久化修改排序规则(后续所有查询均生效)
如果要把调整后的排序永久存储到数据库,可以通过ORM的F表达式做批量更新,仅需3次数据库操作即可完成:
from django.db.models import F target_id = 5 new_order = 3 # 先获取目标记录原来的排序值 old_order = YourModel.objects.values_list('order_number', flat=True).get(id=target_id) if old_order < new_order: # 目标向后移动,中间区间的排序值统一减1 YourModel.objects.filter( order_number__gt=old_order, order_number__lte=new_order ).update(order_number=F('order_number') - 1) elif old_order > new_order: # 目标向前移动,中间区间的排序值统一加1 YourModel.objects.filter( order_number__gte=new_order, order_number__lt=old_order ).update(order_number=F('order_number') + 1) # 最后更新目标记录的排序值 YourModel.objects.filter(id=target_id).update(order_number=new_order)
更新完成后,直接按order_by('order_number')查询就能得到调整后的排序结果。
原有遍历方案的缺陷
遍历全量QuerySet重置排序的方式需要把所有数据库记录加载到Python内存中,数据量较大时会占用极高内存,且Python层排序效率远低于数据库原生排序,仅适合百级以下的小数据集使用。
内容的提问来源于stack exchange,提问作者Ahmed Samy
相关产品推荐
相关产品推荐

