核心是先聚合计算每组每期比率,再用lag()获取上期比率并谨慎处理空值与分组对齐。需确保partition by与group by完全一致,避免整数除法截断,用nullif防除零,预计算时间字段提升性能。

用 LAG() 获取上期值再算比率变化
分组内比率趋势的核心是:先算出每组每个时间点的比率,再和前一期对比。不能直接对原始字段用 LAG(),必须先聚合出比率(比如 SUM(sales)/SUM(total)),再在该结果上应用窗口函数。
常见错误是写成 LAG(sales) OVER (PARTITION BY region ORDER BY month) —— 这拉的是原始销量,不是“当期销售占比”,会导致变化值无业务意义。
- 先用子查询或 CTE 算出每组每期的比率(例如:
ROUND(SUM(amount) * 1.0 / SUM(capacity), 4)) - 在外层对这个比率字段用
LAG(ratio, 1) OVER (PARTITION BY group_col ORDER BY time_col) - 注意
* 1.0或CAST(... AS DECIMAL),避免整数除法截断(尤其在 PostgreSQL/SQL Server 中)
处理首期缺失值与空值传播
LAG() 对每组第一行返回 NULL,直接做减法或除法会把整行变成 NULL。这不是 bug,是设计行为,但容易被忽略导致趋势列全空。
- 用
COALESCE(LAG(ratio) OVER (...), ratio)把首期变化设为 0(即“相比自身无变化”) - 若要计算“环比增长率”,推荐写成:
(ratio - LAG(ratio) OVER (...)) / NULLIF(LAG(ratio) OVER (...), 0),其中NULLIF防止除零 - MySQL 8.0+ 支持
IGNORE NULLS,但多数场景下首期就是该为NULL,强行填充反而扭曲趋势解读
多层级分组时 PARTITION BY 的嵌套逻辑
比如要按 region 和 product_type 分组,再按月看各自比率变化,PARTITION BY 必须包含全部分组维度,且顺序无关,但必须和聚合 GROUP BY 完全一致。
错误写法:PARTITION BY region ORDER BY month(漏了 product_type)→ 会跨品类混算,A品类的上月可能被当成B品类的“上期”。
- 确保窗口的
PARTITION BY和主查询的GROUP BY字段集合完全相同(包括顺序不要求,但字段名、表达式必须一致) - 如果 GROUP BY 里用了
DATE_TRUNC('month', created_at),窗口里也得用同样表达式,不能简写成month - BigQuery 中需注意
GROUP BY别名不能直接用于PARTITION BY,得重复表达式
性能陷阱:ORDER BY 中避免函数或转换
窗口函数的 ORDER BY 子句若含函数(如 TO_CHAR(date, 'YYYY-MM') 或 EXTRACT(YEAR FROM date)),会导致无法利用索引,大表上排序变慢,甚至内存溢出。
- 预先在基础表或 CTE 中生成归一化的时间序号字段(如
year_month INT格式 202401),并在该字段上ORDER BY - PostgreSQL 可建函数索引:
CREATE INDEX idx_orders_ym ON orders ((EXTRACT(YEAR FROM order_date)*100 + EXTRACT(MONTH FROM order_date))) - 确认执行计划中
WindowAgg节点是否走了Sort—— 如果数据量超百万,未优化的排序开销远高于计算本身
实际跑通的关键往往卡在“比率先算准、再拉上期、最后小心空值和分组对齐”这三步。少一个环节,趋势线就可能平白跳变或全空——尤其是跨数据库时,NULL 处理和除法精度差异特别隐蔽。










