用 SQL 给进销存做库存对账:流水汇总、重复单据与负库存三项校验

简介: 库存对账的核心是“期末库存 = 流水合计”,但只核对总数,会漏掉重复单据和中途出现的负库存。本文用 Python + SQLite 建库存流水表和余额快照表,用三条 SQL 分别做三项校验:流水汇总对比余额快照、按单据号查重复单据、用窗口函数 SUM() OVER 计算滚动库存,找出库存变成负数的时间点。文中还附异常处理对照表和数据层的预防做法,适用于 ERP 上线迁移、新旧系统并行和日常差异排查。

进销存或网店ERP用久了,总会遇到“系统库存和实际对不上”的问题。很多人第一反应是去仓库盘点,但盘点只能告诉你“现在差多少”,不能告诉你“哪里出了错”。真正能定位问题的,是库存流水:每一笔入库、出库都是一条记录,把流水按规则校验一遍,大部分错误都能找到源头。

本文用 SQL 实现三项库存对账校验,并用 Python 内置的 SQLite 跑通,读者不需要装数据库就能复制运行。

一、库存对账的基本原理

库存数据有两种形态:

  • 流水:每一笔业务产生的库存变动,比如期初、采购入库、销售出库、退货入库、盘点调整;
  • 快照:某一时刻每个 SKU 的库存余额,也就是系统里显示的“当前库存”。

两者之间有一个恒等关系:

期末库存(快照) = Σ 流水数量(入库为正,出库为负)

对账就是围绕这个关系展开的。不过只校验总数是不够的,下面会看到,总数对得上,也可能藏着错误。

二、三项校验各查什么?

校验 检查内容 能发现的问题
校验一:流水汇总 vs 快照 每个 SKU 的流水合计是否等于期末库存 快照没更新、单据漏过账、手工改库存
校验二:重复单据 同一单据号、同一 SKU 是否出现多次 订单重复导入、接口重复回调
校验三:滚动库存 按时间累计库存,是否出现负数 卖超、出库在入库之前登记、期初录少了

三、完整代码

示例数据里故意埋了三个问题:一张销售单被重复导入、一笔出库导致库存变负、一笔退货入库没有反映到期末快照里。

import sqlite3

conn = sqlite3.connect(":memory:")
cur = conn.cursor()

# 1. 建表:库存流水(每一笔出入库一条记录)和系统期末库存快照
cur.executescript("""
CREATE TABLE stock_flow (
    id        INTEGER PRIMARY KEY,
    sku       TEXT,
    biz_time  TEXT,      -- 业务发生时间
    biz_type  TEXT,      -- 期初/采购入库/销售出库/退货入库/盘点调整
    bill_no   TEXT,      -- 单据号
    qty       INTEGER    -- 入库为正,出库为负
);
CREATE TABLE stock_snapshot (
    sku       TEXT PRIMARY KEY,
    qty       INTEGER    -- 系统显示的期末库存
);
""")

flows = [
    ("FZ-001-WH-M", "2026-11-01 00:00", "期初",     "QC001", 20),
    ("FZ-001-WH-M", "2026-11-01 10:05", "销售出库", "XS101", -3),
    ("FZ-001-WH-M", "2026-11-01 10:05", "销售出库", "XS101", -3),   # 重复导入的同一单
    ("FZ-001-WH-M", "2026-11-02 15:20", "采购入库", "CG201", 10),
    ("FZ-001-WH-M", "2026-11-03 09:00", "销售出库", "XS102", -30),  # 卖超,库存变负
    ("BW-500-BK",   "2026-11-01 00:00", "期初",     "QC002", 8),
    ("BW-500-BK",   "2026-11-02 11:00", "销售出库", "XS103", -2),
    ("BW-500-BK",   "2026-11-03 16:40", "退货入库", "TH301", 1),
]
cur.executemany(
    "INSERT INTO stock_flow (sku, biz_time, biz_type, bill_no, qty) VALUES (?,?,?,?,?)",
    flows)
cur.executemany("INSERT INTO stock_snapshot VALUES (?,?)",
                [("FZ-001-WH-M", -6), ("BW-500-BK", 6)])

def show(title, sql):
    rows = cur.execute(sql).fetchall()
    cols = [d[0] for d in cur.description]
    print(f"\n{title}")
    print("  " + " | ".join(cols))
    for r in rows:
        print("  " + " | ".join(str(x) for x in r))
    if not rows:
        print("  (无)")

# 2. 校验一:流水汇总 = 期末快照?
show("校验一:流水汇总与期末库存不一致的SKU", """
SELECT f.sku,
       SUM(f.qty)              AS flow_total,
       s.qty                   AS snapshot_qty,
       s.qty - SUM(f.qty)      AS diff
FROM stock_flow f
JOIN stock_snapshot s ON s.sku = f.sku
GROUP BY f.sku, s.qty
HAVING SUM(f.qty) <> s.qty;
""")

# 3. 校验二:重复单据(同一单据号、同一SKU出现多次)
show("校验二:疑似重复导入的单据", """
SELECT sku, bill_no, biz_type, COUNT(*) AS times, SUM(qty) AS total_qty
FROM stock_flow
GROUP BY sku, bill_no, biz_type
HAVING COUNT(*) > 1;
""")

# 4. 校验三:用窗口函数计算滚动库存,找出库存变负的时点
show("校验三:滚动库存出现负数的记录", """
SELECT sku, biz_time, biz_type, bill_no, qty, running_qty
FROM (
    SELECT sku, biz_time, biz_type, bill_no, qty,
           SUM(qty) OVER (PARTITION BY sku ORDER BY biz_time, id) AS running_qty
    FROM stock_flow
)
WHERE running_qty < 0;
""")

运行结果:

校验一:流水汇总与期末库存不一致的SKU
  sku | flow_total | snapshot_qty | diff
  BW-500-BK | 7 | 6 | -1

校验二:疑似重复导入的单据
  sku | bill_no | biz_type | times | total_qty
  FZ-001-WH-M | XS101 | 销售出库 | 2 | -6

校验三:滚动库存出现负数的记录
  sku | biz_time | biz_type | bill_no | qty | running_qty
  FZ-001-WH-M | 2026-11-03 09:00 | 销售出库 | XS102 | -30 | -6

窗口函数需要 SQLite 3.25 及以上版本,可以用 import sqlite3; print(sqlite3.sqlite_version) 查看本机版本;用 MySQL 8.0、PostgreSQL 时,三段 SQL 基本可以直接使用。

四、结果解读:为什么一项校验不够?

1. 总数对得上,不代表没问题。
看 FZ-001-WH-M:流水合计是 -6,期末快照也是 -6,校验一判定“一致”。但校验二发现 XS101 被重复导入了一次,多扣了 3 件;校验三发现 11 月 3 日那笔出库让库存变成了负数。快照和流水一起错了,总数自然对得上。 这正是只看总数的盲区。

2. 快照和流水不一致,说明有业务没过账或被手工改过。
看 BW-500-BK:流水合计是 7,快照是 6,差 1 件。对照流水,11 月 3 日有一笔退货入库 +1,快照没有反映出来。实际业务中,这类差异常见的原因是:退货只登记没审核过账、有人直接修改了库存数、接口同步中断。

3. 负库存要追溯到具体时点。
滚动库存能精确定位“哪一笔单据之后库存变负”,比只看最终余额有用得多。负库存可能是真实卖超,也可能是入库单晚登记了,要结合单据时间判断。

4. 三项校验各有边界,用的时候要注意。

  • 校验一用的是内连接,只在流水表或只在快照表里出现的 SKU 不会被检查到。实际使用时,建议再分别查一次“有快照无流水”和“有流水无快照”的 SKU;
  • 校验二可能有误报:同一张销售单里同一个 SKU 分两行录入,也会被列出来,要结合单据明细判断是不是真重复;
  • 校验三依赖业务时间准确,单据时间填错会导致误判。

五、发现问题后怎么处理?

问题 处理方式
重复单据 作废重复的那张,同时排查导入逻辑,用“单据号 + SKU”做唯一约束防止再次发生
负库存(真实卖超) 补采购或联系顾客处理;检查多平台库存同步和安全库存设置
负库存(入库晚登记) 补登入库单,规范“先入库、后出库”的操作顺序
快照与流水不一致 找到未过账的单据补过账;禁止直接改库存数,所有调整走盘点单

预防层面,更有效的是在数据层加约束:单据号加唯一索引,库存调整只能通过单据产生,定期跑一遍这三项校验。

六、在实际系统中怎么用?

成熟的进销存和网店ERP一般会自动维护流水和余额的一致性,并提供库存流水查询、库存台账等报表。这套 SQL 校验更适合以下场景:

  • 系统上线前:迁移历史数据后,验证导入的流水和期初余额是否一致;
  • 多系统并行期间:对比新旧系统导出的流水;
  • 排查疑难差异:把系统导出的流水和余额放进数据库,按上面三项逐一排查。

以网上管家婆为例,网店ERP包含进销存的全部功能(采购、销售、库存、资金、财务),并增加了多平台订单处理和库存同步。系统中可以导出库存流水和库存余额,按本文的表结构整理后即可校验;具体的报表名称和导出字段以所用版本为准。

七、小结

  • 库存对账的核心是“期末库存 = 流水合计”,但只校验总数会漏掉重复单据和负库存;
  • 三项校验互相补充:流水汇总查过账、重复单据查导入、滚动库存查时点;
  • 窗口函数 SUM() OVER (PARTITION BY ... ORDER BY ...) 是计算滚动库存的关键;
  • 预防重于排查:唯一约束、禁止手工改库存、定期跑校验。

作者:俊达软件(广州俊达软件技术有限公司),网上管家婆授权全国经销商,从事进销存与网店ERP的上线实施。

相关文章
|
9天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
3753 37
|
8天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
3344 10
|
3天前
|
云安全 人工智能 安全
|
9天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
1713 6
|
9天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
2039 4
|
云安全 安全 网络安全
|
3天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
557 0
|
22天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
3929 55