sum() over() 不能直接做库存扣减,因其仅为窗口聚合函数,不修改数据且不支持逐行递推逻辑;真实库存水位需通过统一事件模型(入库为正、出库为负)配合 sum() over (order by time) 累计得出。

为什么 SUM() OVER() 不能直接做库存扣减
它只是窗口聚合函数,不修改数据,也不支持“按顺序逐行减去当前订单量”这种带状态的递推逻辑。你看到的“动态扣减”效果,其实是用累计和模拟出来的——把入库当正数、出库当负数,再用 SUM() OVER (ORDER BY time) 算出每条记录后的实时结余。
常见错误是直接写 SUM(quantity) OVER (ORDER BY created_at) 却忽略 quantity 的符号方向:入库必须为正,出库必须为负。否则水位线会一路狂涨或狂跌。
- 入库单记录的
quantity值应为正数 - 出库/销售单记录的
quantity必须存为负数(或用CASE WHEN type='out' THEN -quantity ELSE quantity END转换) - 排序字段(如
created_at)必须有唯一性保障,否则同时间多笔单据会导致窗口计算顺序不确定
如何构造可信赖的库存水位序列
核心是统一事件模型:所有库存变动都落成一行事实记录,含时间戳、变动值、单据类型。然后用 SUM() OVER() 累加。
示例表结构:inventory_log(event_id, sku_id, quantity, event_time, event_type)
SELECT
event_id,
sku_id,
quantity,
event_time,
SUM(quantity) OVER (
PARTITION BY sku_id
ORDER BY event_time, event_id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS stock_level
FROM inventory_log
WHERE sku_id = 'SKU123';
-
PARTITION BY sku_id确保各商品独立计算 -
ORDER BY event_time, event_id解决时间重复问题(event_id作为第二排序键) -
ROWS BETWEEN ... CURRENT ROW显式声明范围,避免某些数据库默认行为差异 - 如果历史初始库存不为零,需在最前面补一条初始化记录(如
quantity = 100,event_time = '1970-01-01')
遇到负库存预警怎么加判断逻辑
不能靠应用层二次查,要在同一查询里完成水位计算 + 预警标记。用 CASE 套在窗口函数外面即可。
SELECT event_id, stock_level, CASE WHEN stock_level
- 预警逻辑必须放在窗口函数外层,否则
CASE会干扰累计过程 - 若需标记“首次跌破0”的那条记录,得用
LAG(stock_level)比较前一行值 - 注意 NULL 处理:如果某条记录
quantity是 NULL,整条累计链会中断,务必提前COALESCE(quantity, 0)
性能与一致性边界在哪
当单 SKU 日增万级日志时,SUM() OVER() 查询延迟会上升,且无法替代事务性扣减。它适合展示、对账、报表,不适合下单时的强一致性校验。
- 高频写入场景下,窗口函数每次执行都要重算全量有序序列,无索引加速
- 真正下单扣减仍需
UPDATE ... SET stock = stock - ? WHERE sku_id = ? AND stock >= ?加行锁 - 建议双轨并行:用窗口函数生成「最终一致」的水位视图供前端展示;用事务 SQL 做「强一致」的扣减执行
- 如果需要毫秒级实时水位,别依赖 SQL 计算,改用 Redis Sorted Set + ZRANGEBYSCORE 维护事件时间线
真正的难点从来不是写对那句 SUM() OVER(),而是想清楚这行结果到底要承担什么职责:是给用户看的趋势图,还是系统做决策的依据。两者混用,迟早踩坑。










