Django QuerySet去重:保留最新记录删除同material__id重复项
解决SQLite下Django QuerySet按material__id去重保留最新记录的问题
我懂你碰到的麻烦了——SQLite对Django的distinct()支持确实有限,尤其是你想按material__id分组,只保留每个分组里最新(排序后的第一条)记录的场景,直接用distinct()会报错很正常。咱们来看看两种可行的解决办法:
方法一:Python内存层面过滤(简单直接,适合小数据量)
你的QuerySet已经按material__id升序、purchase__date降序排好了,每个material__id的第一条就是最新的记录。那我们可以遍历这个QuerySet,记录已经处理过的material__id,只保留第一次出现的条目:
# 先拿到你原有的QuerySet material_purchase = Material_Purchase.objects.order_by("material__id","-purchase__date").values("id","material__id","price","purchase__date") # 过滤重复的material__id,只留最新的第一条 seen_material_ids = set() filtered_results = [] for item in material_purchase: mat_id = item['material__id'] if mat_id not in seen_material_ids: seen_material_ids.add(mat_id) filtered_results.append(item) # filtered_results就是你要的最终结果
这种方法不用改数据库查询逻辑,上手快,适合数据量不大的场景,不会给数据库额外压力。
方法二:数据库层面用窗口函数处理(适合大数据量)
如果你的SQLite版本在3.25及以上(Window函数从这个版本开始支持),可以用Django的Window和RowNumber函数在数据库层面完成过滤,效率更高:
from django.db.models import Window, F from django.db.models.functions import RowNumber # 先给每个material__id分组的条目标注行号,按purchase__date降序排,行号1就是最新的 material_purchase = Material_Purchase.objects.annotate( row_num=Window( expression=RowNumber(), partition_by=F('material__id'), # 按material__id分组 order_by=F('purchase__date').desc() # 组内按日期降序,行号1是最新的 ) ).filter(row_num=1).values("id","material__id","price","purchase__date")
这样直接从数据库返回过滤后的结果,不用在内存里处理大量数据,适合数据量较大的场景。
为什么distinct()不行?
SQLite的DISTINCT语法和PostgreSQL这类数据库不一样,它不支持DISTINCT ON(PostgreSQL的特性),而且Django的distinct(*fields)在SQLite里要求指定的字段必须是order_by里的前缀字段,但你的排序是先material__id再-purchase__date,如果用distinct('material__id'),SQLite会不支持这种跨字段的去重逻辑,所以会报错。
内容的提问来源于stack exchange,提问作者hopieman
相关产品推荐
相关产品推荐

