库存水位是「当前库存 / 近30天日均销量」的比值型指标,需用AVG() OVER(PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW)计算滑动均值,再除以stock_qty;须补全日期、处理NULL/负库存、对齐时间粒度,并区分大促场景。

直接说结论:用 ROW_NUMBER() 或 RANK() 配合 SUM() OVER() 能算出动态库存水位,但真正关键的是定义清楚“水位”——它不是单个字段,而是「当前库存 / 近30天日均销量」或「库存可支撑天数」这类比值型指标,必须用窗口函数先聚合销量再计算。
怎么定义和计算「库存水位」这个指标
电商里没人直接查“库存水位”字段,它本质是业务口径:比如“该商品还能卖几天”,即 stock_qty / avg_daily_sales_30d。难点在分母——日均销量要按商品维度滚动计算,不能全局平均。
- 必须先用
AVG() OVER (PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW)算每个商品每天的30日滑动均值 - 再用该均值除
stock_qty,结果小于1说明已断货风险,大于7可能滞销 - 注意:
sale_date必须连续,缺日期会导致窗口行数不足——得先用GENERATE_SERIES(PostgreSQL)或递归CTE(MySQL 8.0+)补全日期
为什么不能只用 LAG() 或 LEAD() 算水位变化
LAG() 只能取上一行值,适合看“昨天库存 vs 今天库存”,但水位是比值关系,依赖两个动态变量(库存、销量),单靠偏移函数无法解耦。
- 错误写法:
LAG(stock_qty) OVER (...) / LAG(daily_sales) OVER (...)—— 错在分母不是日均,且两列LAG时间点未必对齐 - 正确路径:先用
SUM(sales_qty) OVER (PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW)算累计销量,再除以30得均值 - MySQL 用户注意:
ROWS BETWEEN在 8.0+ 才支持,5.7 必须用自连接模拟窗口
实战中容易被忽略的三个坑
水位计算看着简单,线上跑起来常因数据质量崩掉:
-
stock_qty字段可能为 NULL 或负数(调拨未同步),需提前用COALESCE(stock_qty, 0)和NULLIF(..., 0)防止除零或空值污染结果 - 销量表和库存表时间粒度不一致:销量是按天,库存可能是T+1快照,得用
LAST_VALUE(stock_qty) OVER (PARTITION BY product_id ORDER BY sale_date ROWS UNBOUNDED PRECEDING)拿最新可用库存 - 大促期间日均销量失真,建议加开关:对
is_promotion_day = true的记录单独用AVG() OVER (PARTITION BY product_id, is_promotion_day)分组计算
水位不是静态快照,它依赖销量的时间窗口长度、库存更新延迟、异常值过滤逻辑——这些细节没对齐,算出来的数字看着整齐,实际会误导补货决策。

















