如何在Django中使用__In过滤时保留字符串ID的顺序?
Django过滤后保持输入列表顺序的正确实现
问题核心
使用video__in过滤Video模型时,需让返回结果与传入的字符串类型video_ids列表顺序完全一致,此前因错误使用主键id字段进行Case/When排序,导致类型不匹配报错。
正确实现方案
核心是基于存储字符串ID的video字段构建排序规则,而非主键id。通过Case/When为每个传入的video_id指定对应的排序位置,再用order_by应用该规则即可。
修改后的完整代码
def get_video_objects(video_ids, count): ''' video_ids are already in the correct order, filter should return rows in the same order ''' videos = Video.objects.all() # 基于video字段创建排序映射,匹配每个video_id并分配顺序位置 video_id_order = Case(*[When(video=vid, then=pos) for pos, vid in enumerate(video_ids)]) # 先过滤再按指定顺序排序 results = videos.filter(video__in=video_ids).order_by(video_id_order) results = list(results[:int(count)].values('video', 'title', 'comment_count', 'likes')) return results
关键说明
- 替换了Case/When中的匹配字段:从主键
id(整数类型)改为video(字符串类型),解决了"Field 'id' expected a number but got 'xxx'"的类型错误。 enumerate(video_ids)为每个video_id分配了传入时的顺序位置(pos),order_by(video_id_order)会按照这个位置值从小到大排序,最终结果顺序与video_ids完全一致。- 该方案兼容所有Django支持的数据库(MySQL、PostgreSQL、SQLite等)。
PostgreSQL专属优化方案
如果你的项目使用PostgreSQL,可以利用其原生的array_position函数简化排序逻辑,大列表场景下性能更优:
from django.db.models import Func, F def get_video_objects(video_ids, count): videos = Video.objects.all() results = videos.filter(video__in=video_ids).annotate( # 计算每个video在传入列表中的位置 position=Func(video_ids, F('video'), function='array_position') ).order_by('position') results = list(results[:int(count)].values('video', 'title', 'comment_count', 'likes')) return results
内容的提问来源于stack exchange,提问作者Patt Mehta
相关产品推荐
相关产品推荐

