row_number() 无法实现 fifo 库存扣减,因其仅按时间排序编号,未考虑每笔入库数量是否足够覆盖出库需求;fifo 需累计入库量并截断边界,须用 sum() over () 计算累计量与前置累计量,再匹配出库量范围。

为什么直接用 ROW_NUMBER() 无法正确实现 FIFO 库存扣减
因为 ROW_NUMBER() 只按时间排序生成序号,不考虑每笔入库数量是否足以覆盖当前出库需求。实际 FIFO 要求:逐笔累加入库量,直到满足出库量为止,中间可能只消耗某一笔入库的部分数量。
常见错误现象:ROW_NUMBER() OVER (ORDER BY in_time) 分组后强行取前 N 行,结果要么多扣(忽略单笔入库量限制),要么少扣(跳过部分可用批次)。
关键点在于:必须做「累计求和 + 边界截断」,而不是简单排序取行。
用 SUM() OVER () 累计入库量并定位 FIFO 消耗范围
核心思路是为每笔入库记录计算「截至该笔的累计入库量」,再与当前出库量比较,找出覆盖出库所需的最小连续批次集合。
- 先对入库表按
in_time排序,用SUM(qty) OVER (ORDER BY in_time ROWS UNBOUNDED PRECEDING)得到累计量cum_in_qty - 再计算「该笔入库开始时已有的累计量」即
cum_in_qty - qty,记为prev_cum_in_qty - 对于一笔出库量
@out_qty,满足prev_cum_in_qty 的所有入库记录即为 FIFO 涉及批次
示例片段(SQL Server / PostgreSQL):
SELECT in_id, qty,
SUM(qty) OVER (ORDER BY in_time ROWS UNBOUNDED PRECEDING) AS cum_in_qty,
SUM(qty) OVER (ORDER BY in_time ROWS UNBOUNDED PRECEDING) - qty AS prev_cum_in_qty
FROM inventory_in
WHERE in_time <h3>如何计算每笔入库被实际消耗的数量(支持部分消耗)</h3><p>FIFO 不是“全有或全无”,一笔入库可能只被吃掉一部分。必须根据累计边界反推每笔的真实扣减量。</p>
- 对命中批次中的首笔(
prev_cum_in_qty ),实际消耗 = <code>@out_qty - prev_cum_in_qty - 对中间批次(
prev_cum_in_qty >= @out_qty且cum_in_qty ),实际消耗 = 全量 <code>qty - 对末笔(累计刚好超限),需用
remaining - (cum_in_qty - qty)控制不溢出
难点在状态传递——纯 SQL 难以维护「剩余待扣量」变量,推荐拆成两步:先用窗口函数圈出相关批次,再用 CASE 配合累计值做分段计算。
实际业务中容易被忽略的边界条件
真实库存系统不是理想模型,以下情况必须显式处理:
- 出库量大于总可用库存 → 必须检查
total_in_qty 并报错或置为 0,否则累计逻辑会静默失效 - 多仓库/多商品维度 →
PARTITION BY warehouse_id, item_id必须加在所有OVER子句中,漏写会导致跨仓串货 - 存在退货或调拨冲正 → 入库表不能只含正向
qty > 0记录,需提前过滤或用ABS(qty)+ 类型字段区分方向 - 时间精度问题 → 若
in_time有毫秒级重复,需追加唯一键(如in_id)到ORDER BY中避免窗口排序不稳定
最麻烦的是部分消耗后的余量管理:扣减后那笔入库的剩余数量要落回库存台账,这个动作通常不在单条 SQL 内完成,得靠应用层或存储过程衔接。











