如何用Django的OuterRef筛选同一URL的最新版本CGDSStudy记录?
我有一个名为CGDSStudy的Django模型,包含name、url、version字段:
class CGDSStudy(models.Model): name = models.CharField(max_length=300) url = models.CharField(max_length=300) version = models.PositiveSmallIntegerField(default=1)
同一URL可对应同一研究的多个版本,但我只想获取每个URL对应的最新版本研究记录。以下是三次失败的尝试及问题分析:
失败尝试分析
尝试1
from django.db.models import Max, OuterRef, Exists cgds_studies = CGDSStudy.objects.all() cgds_studies = cgds_studies.filter( Exists(CGDSStudy.objects.annotate(max_version=Max('version')).filter(url=OuterRef('url'), max_version=OuterRef('version'))) )
问题:返回所有记录而非仅最新版本。原因是子查询里的Max('version')没有按url分组,计算的是整个表的最大版本,只要记录版本等于全局最大值就会被保留,而非每个URL自身的最大版本。
尝试2
cgds_studies = cgds_studies.filter( version=Max(CGDSStudy.objects.filter(url=OuterRef('url')).values('version')) )
问题:触发错误"cgdsstudy.version" must appear in the GROUP BY clause or be used in an aggregate function。直接在Max()中传入带OuterRef的子查询不符合Django ORM语法,聚合函数需要配合正确的分组或子查询结构。
尝试3
cgds_studies = cgds_studies.filter( version=CGDSStudy.objects.filter(url=OuterRef('url')).annotate(max_version=Max('version')).values('max_version')[0] )
问题:触发错误This queryset contains a reference to an outer query and may only be used in a subquery.。带OuterRef的子查询不能用索引[0]直接取值,这种操作会试图立即执行查询,无法作为子查询嵌入外层逻辑。
正确解法
方法1:用Subquery获取每个URL的最大版本
通过子查询先拿到每个URL对应的最高版本,再筛选匹配的记录:
from django.db.models import Max, OuterRef, Subquery # 子查询:获取每个url对应的最大版本号 max_version_subquery = CGDSStudy.objects.filter( url=OuterRef('url') ).values('url').annotate(max_v=Max('version')).values('max_v') # 筛选出版本等于对应url最大版本的记录 cgds_studies = CGDSStudy.objects.filter( version=Subquery(max_version_subquery) )
方法2:用Exists反向验证
判断当前记录是否是同URL下的最高版本——如果不存在同URL里版本更高的记录,那它就是最新版本:
from django.db.models import Exists, OuterRef cgds_studies = CGDSStudy.objects.filter( ~Exists( CGDSStudy.objects.filter( url=OuterRef('url'), version__gt=OuterRef('version') ) ) )
方法3:distinct配合排序(限特定数据库)
如果用PostgreSQL这类支持distinct指定字段的数据库,可以按url分组、version降序排序,直接取每个组的第一条记录:
cgds_studies = CGDSStudy.objects.order_by('url', '-version').distinct('url')
注意:MySQL不支持这种distinct用法,谨慎使用。
内容的提问来源于stack exchange,提问作者Genarito

