SQL 与查询优化(PostgreSQL 篇)· 第二期 窗口函数与 CTE 的深度优化 继第一期掌握执行计划与索引基础后,本期聚焦 SQL 的高级能力:窗口函数(Window Functions) 与 公共表表达式(CTE)。 你将学会如何用它们写出更简洁高效的查询,同时避开常见的性能陷阱——包括 CTE 物化屏障、递归 CTE 的优化技巧,以及窗口函数与索引的协作之道。 一、窗口函数 – 不改变行数的聚合利器 1.1 核心概念 窗口函数在保留每一行原始数据的同时,基于一组行(窗口)进行计算。 相比于聚合 GROUP BY(行数减少),窗口函数不压缩结果集。 语法模板: 函数() OVER ( [PARTITION BY 分组列] [ORDER BY 排序列] [ROWS/RANGE 窗口帧子句] ) 常见窗口函数: 类别 函数 用途 排名 ROW_NUMBER(), RANK(), DENSE_RANK() 为行分配序号 偏移 LAG(), LEAD(), FIRST_VALUE(), LAST_VALUE() 访问同一分区内前后行 分布 NTILE(n), PERCENT_RANK() 分桶与百分位 常规聚合 SUM(), AVG(), COUNT(), MAX(), MIN() 移动或累积聚合 1.2 典型高效场景 (1) 每组取 Top N(如每个客户最近 3 笔订单) WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn FROM orders ) SELECT * FROM ranked WHERE rn <= 3; 优化要点: PARTITION BY customer_id 配合索引 (customer_id, order_date DESC) 可实现 仅索引扫描,避免显式排序。 若表巨大,可加 INCLUDE 覆盖其他输出列。 (2) 计算环比/同比(LAG 替代自连接) ❌ 低效的自连接写法: SELECT o1.date, o1.amount, o2.amount AS prev_amount FROM sales o1 LEFT JOIN sales o2 ON o1.date = o2.date + interval '1 day'; ✅ 窗口函数高效写法: SELECT date, amount, LAG(amount, 1) OVER (ORDER BY date) AS prev_amount FROM sales; 自连接会产生嵌套循环或合并连接,而窗口函数只需一次扫描,按序计算。 若 date 字段有唯一索引,可进一步走 Index Only Scan。 (3) 移动窗口聚合(7 日移动平均) SELECT date, amount, AVG(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma_7d FROM sales; ROWS 基于物理行号,适合日期连续场景;若日期有缺失,建议用 RANGE 基于实际时间间隔(需配合 ORDER BY date 且 date 类型可加减)。 1.3 窗口函数的执行计划与优化 执行计划中窗口函数体现为 WindowAgg 节点。 […]