LAG()仅返回前值,不能直接判断趋势;必须显式比较(如sales > LAG(sales) OVER (ORDER BY date))才能识别上升或下降,且需注意NULL、分组、排序唯一性及时间连续性问题。

LAG() 不能直接判断“增长趋势”,只能提供对比用的前值
很多人写 LAG(sales) 就以为趋势出来了,其实它只返回一个数字,不带任何方向含义。真正要识别“是否在增长”,得靠你主动做比较:比如 sales > LAG(sales) OVER (ORDER BY date) 才算一次上升判断。漏掉这个显式比较,LAG() 的结果只是中间数据,不是结论。
常见错误现象:LAG(sales) 返回 NULL 导致整行 CASE 判断进 ELSE 分支,把首日误标为“持平”或“下降”;或者没加 PARTITION BY product_id,不同商品销量被串成一条时间线,趋势完全失真。
- 必须写
OVER (PARTITION BY ... ORDER BY ...),缺一不可 -
ORDER BY字段要有确定性(如加id防重复时间戳导致排序漂移) - 首行
LAG()必为 NULL,别指望数据库自动补逻辑
单期对比用 LAG(),多期连续增长得靠标记 + 分组 ID
想回答“是否连续 3 天增长”,不能只靠 LAG(sales, 1) 和 LAG(sales, 2) 分别比——那只是孤立的两对比较,看不出“连续”。正确路径是:先逐行打标记,再构造连续段落 ID。
实操建议:
- 用
CASE WHEN sales > LAG(sales) OVER w THEN 1 ELSE 0 END AS is_up标出每期是否比前一期高 - 用
SUM(1 - is_up) OVER w AS group_id把连续的1归到同一组(这是关键技巧,不是直觉能想到的) - 最后按
group_id聚合,COUNT(*) >= 3就是你要的连续增长段
注意:ROW_NUMBER() 或 RANK() 在这里没用——它们排的是顺序位置,不是趋势连续性。数据有重复值时,RANK() 会跳号,直接破坏连续判断逻辑。
时间不连续时 LAG() 会拿错“上一期”,必须先补日期维度
原始表里如果缺 2023-02-15 这天的数据,LAG(sales, 1) OVER (ORDER BY date) 会从 2023-02-16 直接跳到 2023-02-14,你以为是“昨日环比”,实际是“前日环比”。这种错位在周报、月报场景下尤其致命。
解决办法不是硬调 offset,而是补全业务时间轴:
- 用
GENERATE_SERIES()(PostgreSQL)、UNNEST(ARRAY[...])(BigQuery)或自连接生成完整日期序列 - 再用
LEFT JOIN把销售数据挂上去,空缺日填0或NULL - 最后在补全后的结果集上开窗,
LAG()才真正对应“逻辑上前一天”
MySQL 用户注意:8.0+ 支持窗口函数但不支持 GENERATE_SERIES,得用递归 CTE 或临时日历表替代。
计算增长率时 LAG() 值参与除法,必须防 NULL 和除零
写 (cur - LAG(cur)) / LAG(cur) 是危险操作:首行 LAG() 是 NULL,整列变 NULL;某期 LAG(cur) = 0,直接报 division by zero 错误。
安全写法只有两个核心动作:
- 给
LAG()显式设默认值,例如LAG(sales, 1, 0.01)(注意类型匹配,别写'0'引发隐式转换) - 用
NULLIF(LAG(sales), 0)包裹分母,让除零变 NULL,再配合ROUND(..., 4)控制精度 - 别在同一个 SELECT 里重复写三次
LAG(sales),性能差还易错——先用子查询或 CTE 提前算好prev_sales
最易被忽略的一点:如果你的业务允许“零值作为有效基数”(比如成本为 0 时利润率无意义),那 NULLIF 就不够,得结合业务规则加额外条件过滤。










