如何在Django中不使用原生SQL实现嵌套窗口函数查询?
解决Django中窗口函数查询过滤rn=1的问题
你遇到的报错是因为数据库不允许在WHERE子句中使用窗口函数——窗口函数的计算是在WHERE过滤、分组等逻辑之后执行的,所以直接用filter(rn=1)会触发Window function is allowed only in SELECT list and ORDER BY clause错误。下面是几种不需要写完整原生SQL的可行方案:
方案1:用Subquery+Max获取分组最新记录(推荐,兼容性好)
这种方法避开窗口函数,通过分组取每个组合的最大captured_date,再关联主查询筛选对应记录,逻辑和原SQL一致:
from django.db.models import Subquery, OuterRef, Max # 构造子查询:获取每个(check_in_date, price_type_id, room_category_id)分组的最新captured_date latest_captured_subquery = Prices.objects.filter( check_in_date=OuterRef('check_in_date'), price_type_id=OuterRef('price_type_id'), room_category_id=OuterRef('room_category_id') ).values('check_in_date', 'price_type_id', 'room_category_id').annotate( max_captured=Max('captured_date') ).values('max_captured') # 主查询:筛选符合条件且captured_date为分组最新的记录 result = Prices.objects.filter( room_category_id=req['room_category_id'], price_type_id=req['price_type_id'], check_in_date__gte=check_in_date, check_in_date__lte=check_out_date, captured_date=Subquery(latest_captured_subquery) ).values( 'id', 'session_hash', 'user_id', 'price_type_id', 'room_category_id', 'check_in_date', 'price', 'captured_date' )
优点:
- 完全使用Django ORM语法,不需要手写SQL片段
- 兼容性好,支持Django 1.11+
- 数据库执行效率通常较高,分组取MAX的逻辑比窗口函数更轻量化
方案2:用Django Window函数+子查询过滤
如果更贴近原SQL的窗口函数逻辑(Django 2.0+支持Window函数),可以先给记录标注行号,再通过子查询筛选行号为1的记录:
from django.db.models import Window, F, Subquery, OuterRef from django.db.models.functions import RowNumber # 第一步:给符合条件的记录标注行号rn annotated_prices = Prices.objects.filter( room_category_id=req['room_category_id'], price_type_id=req['price_type_id'], check_in_date__gte=check_in_date, check_in_date__lte=check_out_date ).annotate( rn=Window( expression=RowNumber(), partition_by=[F('check_in_date'), F('price_type_id'), F('room_category_id')], order_by=F('captured_date').desc() ) ) # 第二步:通过子查询筛选rn=1的记录ID,再关联主查询获取完整字段 result = Prices.objects.filter( id__in=Subquery(annotated_prices.filter(rn=1).values('id')) ).values( 'id', 'session_hash', 'user_id', 'price_type_id', 'room_category_id', 'check_in_date', 'price', 'captured_date' )
优点:
- 逻辑和原SQL完全对应,易于理解
- 利用Django原生的Window函数支持,避免手写SQL片段
方案3:参数化的原生查询(备选)
如果上述ORM方案都不符合需求,也可以用参数化的原生查询(比直接写拼接SQL安全),这种方式最贴近原SQL结构:
result = Prices.objects.raw(''' SELECT id, session_hash, user_id, price_type_id, room_category_id, check_in_date, price, captured_date FROM ( SELECT p.*, Row_number() OVER (partition BY check_in_date, price_type_id, room_category_id ORDER BY captured_date DESC) AS rn FROM dashboard_prices p WHERE room_category_id = %s AND price_type_id = %s AND check_in_date >= %s AND check_in_date <= %s ) p WHERE rn = 1 ''', [req['room_category_id'], req['price_type_id'], check_in_date, check_out_date])
优点:
- 完全还原原SQL逻辑,调试方便
- 参数化查询避免SQL注入风险
内容的提问来源于stack exchange,提问作者burakko




