不能直接用 GROUP BY 算库存周转率,因为其需期初与期末库存计算平均库存,而 GROUP BY 无法在单行中获取同一商品跨期的前后库存值;窗口函数 LAG/LEAD 可保留明细行并访问相邻时间点数据,配合 NULLIF 防除零,才能准确计算周转率。

为什么不能直接用 GROUP BY 算库存周转率
库存周转率 = 销售成本 / 平均库存,而平均库存需要期初库存 + 期末库存除以 2。如果只用 GROUP BY 按商品聚合,就拿不到同一商品在不同时间点的「前一行」和「后一行」库存值——比如某商品在 2024-01 和 2024-02 的库存,无法在一条记录里同时看到,自然算不出跨期平均值。
窗口函数能保留明细行,同时访问相邻时间点的数据,这才是关键。
LAG() 和 LEAD() 怎么取前后库存值
假设你有按 product_id 和 report_date 排序的月度库存快照表 inventory_snapshot,每行含 stock_qty(当期期末库存)和 cost_of_goods_sold(当期销售成本):
SELECT product_id, report_date, stock_qty, LAG(stock_qty) OVER (PARTITION BY product_id ORDER BY report_date) AS prev_stock, LEAD(stock_qty) OVER (PARTITION BY product_id ORDER BY report_date) AS next_stock, cost_of_goods_sold FROM inventory_snapshot;
-
LAG(stock_qty, 1)默认取前 1 行,即上月期末库存,可当作本月期初库存 -
LEAD(stock_qty, 1)取下月期末库存,和本月期末库存一起算「本月平均库存」更合理(因为平均库存通常定义为期初 + 期末 / 2) - 注意:
PARTITION BY product_id防止跨商品错位;ORDER BY report_date必须存在且无重复,否则排序不确定
怎么算出可落地的周转率数值
真正可用的周转率,要基于「滚动 12 个月」或「最近两期」计算,避免单月异常值干扰。常见写法是用 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW 计算移动平均,但库存周转率本身不推荐移动窗口——它本质是「期间指标」,不是「时点指标」。
更稳妥的做法是:先用 LAG 拿到期初,再显式计算:
SELECT product_id, report_date, cost_of_goods_sold / NULLIF((LAG(stock_qty) OVER w + stock_qty) / 2, 0) AS turnover_rate FROM inventory_snapshot WINDOW w AS (PARTITION BY product_id ORDER BY report_date);
-
NULLIF(..., 0)防止除零错误——平均库存为 0 时返回 NULL,比报错更可控 - 第一期(最早日期)的
LAG返回 NULL,对应周转率也为 NULL,符合业务逻辑(无期初库存不可算) - 不要用
AVG(stock_qty) OVER (... ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)替代,那算的是「两个时点库存的平均值」,不是「期初+期末/2」,语义不符
性能和精度容易被忽略的坑
窗口函数本身不慢,但数据量大时,PARTITION BY 的分组键选择、ORDER BY 字段的索引缺失,会让执行计划退化成全表排序。尤其当 report_date 是 TEXT 类型或带时分秒时,排序开销陡增。
- 确保
(product_id, report_date)有联合索引,且report_date是DATE类型(非TIMESTAMP) - 如果原始表没有严格按月快照,而是每日库存,需先用
WHERE report_date IN (SELECT MAX(report_date) FROM ... GROUP BY product_id, YEAR(report_date), MONTH(report_date))聚合成月度粒度,再算周转率 - 财务口径中「销售成本」可能来自另一张表,JOIN 时务必确认时间对齐逻辑——是取「当月销售成本」还是「对应库存周期的销售成本」?错配会导致结果偏差 30% 以上
真正难的从来不是写对窗口函数,而是确认业务定义里的「期初」「期末」「成本归属期」在数据中是否真实存在、对齐、无空缺。

















