窗口函数可缓解90%报表性能问题,但需写对over子句;lag/lead单次扫描完成偏移计算,优于多次扫描的自连接;必须用确定性排序如order by id,时间字段需加唯一列兜底;row_number()不重复编号适合分页,rank()并列跳号适合排行榜。

直接用窗口函数替代子查询,90% 的报表性能问题能当场缓解;但必须写对 OVER 子句,否则结果错、性能差、还查不出原因。
为什么LAG/LEAD比自连接快得多
自连接要扫描表两次(甚至更多),而 LAG 和 LEAD 在单次全表或索引扫描中完成偏移计算。Oracle 不会物化中间结果,也不触发谓词下推失效。
- 错误写法:
SELECT t1.val, (SELECT t2.val FROM tab t2 WHERE t2.id = t1.id - 1) AS prev_val FROM tab t1→ 每行都执行一次子查询,逻辑读爆炸 - 正确写法:
SELECT val, LAG(val) OVER (ORDER BY id) AS prev_val FROM tab→ 一次扫描,内存中滑动窗口计算 - 关键约束:必须有确定性排序,
ORDER BY id安全,ORDER BY create_time危险(时间精度不足时顺序不确定) - 补救方式:加唯一列兜底,如
ORDER BY create_time, log_id
ROW_NUMBER() vs RANK():别在TOP-N里用错
两者语义不同,混用会导致业务逻辑出错,且无法通过执行计划发现——错误只在结果集里暴露。
-
ROW_NUMBER()强制编号不重复,适合分页、抽签、去重取首行 -
RANK()并列同名、跳号,适合销售排行榜(两个第3名后是第5名) - 典型误用:
WHERE rn 套在 <code>RANK()上,若前10名中有并列,实际返回超10行 - 安全写法:TOP-10 严格取10行,一律用
ROW_NUMBER() OVER (ORDER BY sales DESC)
累计求和必须配ROWS,别信RANGE
SUM() OVER 默认用 RANGE 语义,一旦排序键有重复值,就会跨行累加,结果错;且 RANGE BETWEEN 无法走索引,性能差。
- 危险写法:
SUM(amount) OVER (PARTITION BY dept ORDER BY hire_date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 安全写法:
SUM(amount) OVER (PARTITION BY dept ORDER BY hire_date, emp_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 真正提速点:加上
emp_id后,排序唯一,ROWS可精确锚定物理位置,Oracle 能向量化处理 - 分区表场景:若按日分区,
hire_date已天然有序,可省略emp_id,但需确认该字段无 NULL
窗口函数不能出现在WHERE/HAVING,这是硬限制
所有分析函数都在 SELECT 阶段计算,WHERE 是在之前过滤的。想“筛选排名前3的部门”,不能写 WHERE RANK() 。
- 错误:
SELECT dept, SUM(sal) s, RANK() OVER (ORDER BY s DESC) r FROM emp GROUP BY dept WHERE r → 报错 <code>ORA-30483: window functions are not allowed here - 正确:套一层子查询或 CTE,
WITH ranked AS (SELECT dept, SUM(sal) s, RANK() OVER (ORDER BY SUM(sal) DESC) r FROM emp GROUP BY dept) SELECT * FROM ranked WHERE r - 注意:CTE 不是语法糖,Oracle 12c+ 对命名查询块有优化,但 11g 必须用嵌套 SELECT
最易被忽略的是排序键的确定性——它不报错、不告警,但会让同一SQL多次执行结果不一致,尤其在报表导出或定时任务里埋雷。别只盯着函数名,先盯死 ORDER BY 里的字段组合是否全局唯一。











