实时库存余额需动态计算为“总入库量-已销售量-已占用量”,不可依赖goods表静态库存字段;须用子查询或WITH语句聚合stock_in、order_items、reservations三张表,并用COALESCE处理空值。

用子查询算出实时库存余额再筛选不足的商品
直接查 goods 表的“库存字段”不可靠——它通常是静态值,没扣减已下单未出库的量。真正不足得看「当前总入库量 - 已销售量 - 已占用量」。必须用嵌套查询动态算。
常见错误是只联 stock_in 和 orders,漏掉 order_items 中的未发货明细,或忽略 stock_adjust(手工调账)导致结果偏高。
- 外层只保留
SELECT *或关键字段,别在最外层加复杂计算,影响可读性和索引使用 - 子查询里统一用
COALESCE(SUM(...), 0),避免SUM遇空组返回NULL导致整个余额变NULL - 如果数据库是 MySQL 5.7 或更早,子查询不能直接引用外层表别名(即不支持相关子查询),得改用
JOIN方式重写
MySQL 8+ 推荐写法:用 WITH 定义中间库存视图
比多层嵌套更清晰,也方便复用和调试。重点是把「可用库存」拆成三块独立聚合:入库、销售出库、预留占用。
WITH stock_summary AS (
SELECT
g.id,
g.name,
COALESCE(SUM(si.quantity), 0) AS total_in,
COALESCE(SUM(oi.quantity), 0) AS sold_out,
COALESCE(SUM(r.quantity), 0) AS reserved
FROM goods g
LEFT JOIN stock_in si ON g.id = si.goods_id AND si.status = 'done'
LEFT JOIN order_items oi
ON g.id = oi.goods_id
AND oi.order_id IN (SELECT id FROM orders WHERE status IN ('paid', 'confirmed'))
LEFT JOIN reservations r ON g.id = r.goods_id AND r.status = 'active'
GROUP BY g.id, g.name
)
SELECT id, name, total_in - sold_out - reserved AS available_stock
FROM stock_summary
WHERE total_in - sold_out - reserved < g.min_stock;注意:g.min_stock 是商品表里的安全库存阈值,别写成常量 0——很多业务要求低于 5 件才预警,不是“为 0 才告警”。
PostgreSQL 或 SQL Server 怎么处理 NULL 占位问题
当某商品完全没入库记录时,LEFT JOIN 后 si.quantity 全是 NULL,SUM 结果也是 NULL,直接参与减法会让整行消失(因为 WHERE 条件里 NULL 永远不成立)。
- 必须用
COALESCE(SUM(...), 0),不能只用COALESCE(..., 0)包单个字段 - PostgreSQL 支持
FILTER子句,可替代条件JOIN,例如:SUM(oi.quantity) FILTER (WHERE oi.status != 'cancelled') - SQL Server 用户注意:
STRING_AGG或窗口函数在汇总前可能干扰分组逻辑,这类查询里尽量避免混用
性能卡在哪?三个最容易被忽视的索引点
这个查询慢,90% 不是因为写法,而是缺索引。尤其当 order_items 表超百万行时,没索引等于全表扫三次。
-
stock_in(goods_id, status)—— 加速状态过滤和分组 -
order_items(goods_id, order_id)—— 联查时避免回表;如果还按订单状态过滤,加status到联合索引最后 -
reservations(goods_id, status)—— 预留占用通常高频变动,单独建索引比走主键快得多
别依赖“商品 ID 主键自动加速”,主键只对单表精确查找有效,JOIN + GROUP BY 场景下,复合索引顺序和覆盖字段才是关键。上线前务必用 EXPLAIN 看是否走了索引,而不是只看结果对不对。

















