You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

火山引擎 最新活动