因为group by会打乱时序并聚合掉明细,无法追踪批次消耗顺序;fifo必须按时间逐笔匹配,需用sum() over (order by time)累计净量并结合lag/自连接定位具体批次。

为什么直接用 GROUP BY 算不出 FIFO 库存余额
因为 FIFO 要求按入库时间顺序逐笔消耗,而 GROUP BY 会打乱时序、聚合掉明细,根本无法追踪“哪一批货先出”。窗口函数能保留行级顺序并累积计算,这才是解题关键。
核心思路是:把「入库」记为正数、「出库」记为负数,按时间排序后用 SUM() OVER (ORDER BY time) 算累计余量,再结合每笔出入库量,倒推出该笔实际消耗/释放的是哪几批库存。
用 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 模拟逐笔匹配
FIFO 不是简单累加,而是“当前出库量优先扣减最早未清空的入库批次”。需要两层窗口逻辑:
- 第一层:用
ROW_NUMBER() OVER (PARTITION BY type ORDER BY time)给所有入库(type = 'in')和出库(type = 'out')分别编号,确保各自有序 - 第二层:对每笔出库,用
SUM(amount) OVER (ORDER BY time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)算到当前行为止的净库存,再反向关联到最早那批仍有余量的入库记录 - 真正实用的做法是:先用
LAG()或自连接生成“可用库存快照”,再用CASE WHEN+ 窗口累计判断该笔出库是否耗尽某批入库——这比硬套单个窗口函数更稳
避免 timestamp 精度导致的 ORDER BY 错序
多个出入库操作若发生在同一秒甚至同一毫秒,ORDER BY time 无法保证稳定顺序,FIFO 结果就会随机波动。必须补一个确定性排序字段:
- 加自增
id字段,写成ORDER BY time, id - 如果没有主键,至少加
ORDER BY time, ctid(PostgreSQL)或ORDER BY time, (SELECT NULL)(不推荐,仅应急) - MySQL 8.0+ 中若用
datetime(6),要确认写入时真带微秒,否则仍可能撞钟 - 别依赖应用层插入顺序——事务并发下 SQL 层看到的顺序≠写入顺序
PostgreSQL vs MySQL 8.0 的关键语法差异
两者都支持标准窗口函数,但处理 FIFO 这类依赖前序状态的逻辑时,细节很伤人:
- PostgreSQL 支持
GENERATE_SERIES()配合LATERAL拆分一笔大出库为多行虚拟消耗,MySQL 不行;得用递归 CTE(WITH RECURSIVE),但性能差、有深度限制 - MySQL 对
ROWS BETWEEN的边界解释更严格,遇到NULLtime 会直接跳过该行参与窗口计算;PostgreSQL 默认把NULL排在最前,需显式写ORDER BY time NULLS LAST -
LAST_VALUE()在 MySQL 中默认不包含当前行(RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),PostgreSQL 默认包含,不统一写法容易算错最后一批剩余量
真实业务里,10 万行以上数据做纯 SQL FIFO 计算,响应时间很容易从毫秒级跳到秒级——不如在应用层用游标分批拉取入库明细,内存里跑一次链表匹配,反而更可控。











