波峰是当日价格严格大于前一天和后一天价格,波谷是当日价格严格小于前一天和后一天价格;需用ORDER BY明确排序,配合LAG()和LEAD()获取邻值,用COALESCE处理边界NULL,且不可在WHERE中直接引用窗口结果。

怎么用 LAG() 和 LEAD() 判断波峰波谷
波峰波谷本质是局部极值:某行数值比前一行和后一行都大(波峰),或都小(波谷)。PostgreSQL 没有内置“找极值”函数,但 LAG() 和 LEAD() 可以安全获取相邻行值——前提是明确排序逻辑,否则结果无意义。
关键点:必须用 ORDER BY 定义窗口顺序,且该列需具备业务连续性(比如时间戳、序号)。若排序列有重复值,需加二级排序(如 ORDER BY ts, id)避免非确定性。
实操建议:
- 始终在
OVER (ORDER BY ...)中显式声明排序,不依赖表物理顺序 - 用
COALESCE()处理首尾行的NULL:首行无前驱,LAG()返回NULL;末行无后继,LEAD()同理 - 避免在
WHERE中直接过滤波峰波谷标记——窗口函数不能在WHERE中引用,得套一层子查询或 CTE
波峰波谷标记的 SQL 写法示例
假设有一张股票分钟级行情表 tick_data,含字段 ts(时间)、price(价格):
SELECT
ts,
price,
CASE
WHEN price > COALESCE(LAG(price) OVER (ORDER BY ts), -INF)
AND price > COALESCE(LEAD(price) OVER (ORDER BY ts), -INF)
THEN 'peak'
WHEN price < COALESCE(LAG(price) OVER (ORDER BY ts), +INF)
AND price < COALESCE(LEAD(price) OVER (ORDER BY ts), +INF)
THEN 'trough'
ELSE NULL
END AS extremum
FROM tick_data;注意:-INF 和 +INF 是示意,实际用 -(10^10) 或 999999999999.99 替代更稳妥;PostgreSQL 不支持字面量 INF 在普通表达式中。
常见错误现象:漏掉 COALESCE 导致整行标记为 NULL——因为 NULL > 100 结果是 NULL,不是 FALSE,而 CASE 只匹配 TRUE 分支。
为什么不能只比前一行或后一行
单靠 LAG() 只能识别“上升沿”(当前 > 前),单靠 LEAD() 只能识别“下降沿”(当前 > 后)。波峰要求同时满足两个条件,缺一不可。漏判会把斜坡上的点误标为峰/谷。
性能影响很小:LAG()/LEAD() 是轻量级窗口函数,仅扫描一次数据,不触发额外排序(只要 ORDER BY 列有索引)。
使用场景限制:
- 不适合高频抖动数据(如原始传感器噪声),需先平滑(移动平均)再分析
- 对等值平台(连续多行 price 相同)无法标记——严格定义下,平台既非波峰也非波谷;若需识别“平台顶/底”,得改用
MIN()/MAX()窗口聚合配合范围帧
对比前后行时容易忽略的边界与精度问题
浮点数比较必须小心。如果 price 是 NUMERIC 类型还好,若是 REAL 或 DOUBLE PRECISION,直接 > 可能因精度丢失导致误判。建议统一转为 NUMERIC 或用差值阈值判断:
ABS(price - LAG(price) OVER (ORDER BY ts)) > 0.001
另一个易忽略点:时间间隔不均。若 ts 缺失或跳跃大(如隔了 5 分钟才一条记录),用相邻行比较会漏掉中间真实波峰。此时应先用 GENERATE_SERIES() 补全时间序列,再插值或填充,否则分析结果反映的是采样偏差,不是真实走势。
复杂点永远在数据质量本身——窗口函数只是工具,它不会替你决定“多大算波动”“多久算趋势”。

















