如何用Django生成指定GROUP BY查询以处理重复项目数据
解决Django数据库视图去重并按聚合字段排序的问题
嘿,我完全懂你遇到的麻烦——你需要从ProjectName这个数据库视图里取出去重后的项目,把已使用(hasbeenused为1)的排在最前面,还要适配ModelChoiceField不接受重复数据的要求。之前的ORM查询因为排序字段选得不对,导致GROUP BY里多了不必要的字段,我来帮你搞定!
问题根源
你之前写的order_by("-hasbeenused")用的是原始字段,而不是你通过annotate生成的聚合字段max。Django的ORM为了兼容PostgreSQL这类严格遵循SQL标准的数据库,会自动把排序中用到的非聚合字段加入GROUP BY子句,这就是为什么会出现多余的"timeentry_project"."hasbeenused"分组字段。
正确的ORM解决方案
你需要基于聚合出来的max字段排序,同时把name字段包含在values里(毕竟要在选择框里显示项目名称)。代码如下:
from django.db.models import Max # 获取去重并按使用状态排序的项目查询集 project_queryset = ProjectName.objects.values("id", "name") \ .annotate(max_hasbeenused=Max("hasbeenused")) \ .order_by("-max_hasbeenused")
这段代码生成的SQL会和你需要的几乎一致:
SELECT "timeentry_project"."id", "timeentry_project"."name", MAX("timeentry_project"."hasbeenused") AS "max_hasbeenused" FROM "timeentry_project" GROUP BY "timeentry_project"."id", "timeentry_project"."name" ORDER BY "max_hasbeenused" DESC;
从你的数据示例能看出来,同一个id对应的name肯定是相同的,所以把name加入GROUP BY完全安全,不会导致分组错误,还能拿到项目名称用于显示。
适配ModelChoiceField
ModelChoiceField默认需要模型实例,但values()返回的是字典查询集,这里给你两种适配方式:
- 转换成
(id, name)元组列表直接用:
project_choices = [(item['id'], item['name']) for item in project_queryset] # 表单中使用 class YourForm(forms.Form): project = forms.ChoiceField(choices=project_choices)
- 用原生SQL返回模型实例,适配
ModelChoiceField:
project_queryset = ProjectName.objects.raw(""" SELECT "timeentry_project"."id", "timeentry_project"."name", MAX("timeentry_project"."hasbeenused") AS max FROM "timeentry_project" GROUP BY "timeentry_project"."id" ORDER BY max DESC; """) # 表单中使用 class YourForm(forms.Form): project = forms.ModelChoiceField(queryset=project_queryset, empty_label=None)
这样就能完美满足你的需求:去重后的项目按已使用状态排序,同时能被ModelChoiceField正常接受。
内容的提问来源于stack exchange,提问作者wernhervb
相关产品推荐
相关产品推荐

