窗口函数计算“连续多期库存超标”应使用rows between与count(*) filter配合,而非lag/lead硬编码;用percent_rank()按品类排名识别异常高库存sku更鲁棒。

窗口函数怎么算“连续多期库存超标”
直接用 LAG() 或 LEAD() 拉出前N期库存值,再配合布尔聚合判断是否连续超标——这是最稳的路子。别用自连接或子查询模拟,性能掉得厉害,尤其在千万级出入库流水表上。
常见错误是写成:WHERE LAG(stock_qty, 1) > safety_stock AND LAG(stock_qty, 2) > safety_stock,这只能判2期,且无法动态扩展。正确做法是用 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 配合 COUNT(*) FILTER (WHERE stock_qty > safety_stock):
SELECT
product_id,
dt,
stock_qty,
COUNT(*) FILTER (WHERE stock_qty > safety_stock)
OVER (PARTITION BY product_id ORDER BY dt ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS overstock_cnt
FROM inventory_log;
注意点:
-
ROWS BETWEEN必须配ORDER BY,否则窗口无序,结果不可靠 - PostgreSQL 支持
FILTER,MySQL 8.0+ 得改用SUM(CASE WHEN ... THEN 1 ELSE 0 END) - 时间字段
dt不能有重复值,否则需加id做二级排序,不然同天多条记录会乱序
如何用 PERCENT_RANK() 找出“异常高库存SKU”
不是所有库存高都该预警——有些是季节性备货。用 PERCENT_RANK() 对每个品类内SKU按当前库存做相对排名,比绝对阈值更鲁棒。
典型场景:某品类有500个SKU,想定位库存排前5%且近30天无出库的SKU。操作分两步:
- 先按
PARTITION BY category计算PERCENT_RANK() OVER (PARTITION BY category ORDER BY stock_qty DESC) - 再过滤
percent_rank
坑点:
-
PERCENT_RANK()最小值恒为0,最大值是(n-1)/(n-1)=1,但不会真达到1——所以用比 <code> 更稳妥 - MySQL 不支持
PERCENT_RANK()的FILTER修饰,必须先用子查询或 CTE 把品类数据切出来再算 - 如果某品类只有3个SKU,
PERCENT_RANK()结果只能是 0, 0.5, 1 ——这时前5%实际只命中第一个,逻辑依然成立,但业务方容易误读
AVG() OVER 和 EXPONENTIAL SMOOTHING 哪个更适合预测积压趋势
简单移动平均(AVG() OVER)够用,除非你明确需要衰减权重。强行套指数平滑反而增加维护成本,还难解释。
实操建议:
- 用
AVG(stock_qty) OVER (PARTITION BY product_id ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)算7日均值,平滑日常波动 - 若要模拟衰减,可用
0.5 * curr + 0.25 * lag1 + 0.125 * lag2 + 0.125 * lag3手动加权(注意权重和必须为1) - SQL里没法直接写
EXPONENTIAL SMOOTHING的递归公式,非得用PL/pgSQL或存储过程,得不偿失
性能影响明显:窗口越宽,ROWS BETWEEN 越大,执行计划容易从索引扫描退化为全表扫描。建议对 (product_id, dt) 建复合索引。
为什么 RANK() 在库存周转率排序里会漏掉关键SKU
因为 RANK() 对相同周转率值赋予相同名次,跳过后续名次——比如两个SKU并列第1,下一个就是第3。而库存优化关注的是“最慢的那批”,需要连续编号才能取TOP N。
正确选择:
- 用
ROW_NUMBER() OVER (ORDER BY turnover_rate ASC)获取严格递增序号 -
DENSE_RANK()可以保留并列,但不跳号,适合做分组标签(如“最慢档位”),不适合取前100 - 别用
RANK()做WHERE rank ,否则可能只返回98行甚至更少
真实案例:某仓用 RANK() 取“周转最慢前100”,结果因23个SKU并列第1,实际只捞出78个,漏掉的全是长尾低频件——这些恰恰是积压主力。











