两份库存CSV核对:按编号找差异,先拦截重复键

两份库存表都是四行,逐行配对也没有报错,仍可能把不同商品放在一起比较。本次样本的第一行分别是A-001和C-003;真正相同的编号分散在其他位置。另一份样本含重复编号,直接连接后,两行对两行变成了四条结果。

这篇教程交付一个只读核对工具:先检查两份文件是否满足“一编号一条库存”的约定,再按编号分出相同、变化、仅左侧和仅右侧四组。报告保存两边的数量及输入摘要,帮助你回查差异;它不向仓库系统提交修改。

下载完整核对包:脚本、两份样本、运行器、观察结果与8项测试(ZIP)

先运行两份顺序不同的文件

解压进入csv-reconcile-v1,需要Python 3.11+,不用安装第三方包。本轮实测macOS、Python 3.14.4;演示重复连接时使用Python自带SQLite 3.53.2,仅为内存实验。

python3 run_lab.py
python3 -m unittest -v
python3 reconcile.py fixtures/left.csv fixtures/right.csv

运行器会在临时目录调用真正的命令行程序,检查正常报告、重复编号被拒绝、旧报告保护和重复键连接对照。最后一条命令只预览JSON,不创建报告文件。

左表行序 数量 右表行序 数量
A-001 10 C-003 0
B-002 4 A-001 8
C-003 0 D-004 5
001 2 1 2

这里两边长度相同,因此zip(..., strict=True)也不会替你检查编号是否对应。strict解决的是长度不一致,不是业务主键匹配。按行得到的第一对是A-001与C-003,第二对是B-002与A-001;它们不是同一商品的两个数量。

工具按编号核对,实际得到以下六条记录:

编号 左数量 右数量 结果
001 2 缺席 仅左侧
1 缺席 2 仅右侧
A-001 10 8 变化
B-002 4 缺席 仅左侧
C-003 0 0 相同
D-004 缺席 5 仅右侧

汇总为相同1、变化1、仅左侧2、仅右侧2。两边共有的编号只有两个,并集是六个,所以报告不是机械保持四行。C-003的0是读到的实际数量;JSON中的null才表示那一边没有此编号,不能把两者合并。

重复键不是排序问题

演示另建了两张临时SQLite表:左边A有10和11两条,右边A有12和13两条。真实执行按sku连接后得到10/12、10/13、11/12、11/13四组组合。这个结果符合连接规则,却不符合“一商品一条库存”的核对目标。

换成字典也未必安全。如果直接用后读入的值覆盖同名键,文件排列会决定保留10还是11,而程序没有证据作这个决定。本工具在任意一边发现重复SKU时拒绝整个比较;即使两条数量相同也拒绝,不提供静默取第一条、取最后一条或相加的开关。

可以另存下面内容为新的duplicate.csv复现:

sku,stock
A,10
A,11
python3 reconcile.py duplicate.csv fixtures/right.csv --output blocked.json

本次运行返回2,错误中包含DUPLICATE_SKU、当前记录位置和第一次出现的记录序号,blocked.json没有生成。修正源数据后,应重新核对两份文件;不能把部分记录当作完整库存报告交给下游。

重复也可能揭示键选错了。例如每个仓库各有一条A,真正唯一的可能是“仓库+SKU”,而不是SKU。这时应先调整导出范围或设计复合键,不能删掉其中一行来满足本工具。本包只接受下面的两列契约,没有实现多仓库合并。

编号与数量采用不同的解释规则

表头必须按顺序恰好为sku,stock,文件为UTF-8,可带BOM。每份至少一条记录,最多2MiB、10000条;CSV结构、编码或任一记录不合格,都不生成最终报告。输入会读入内存,这些上限不是性能承诺。

字段 本包规则 实际含义
sku 1—64位ASCII字母、数字、下划线、点或短横线;首位须为字母或数字 保留前导零,区分大小写,不自动去空白
stock 最多10位ASCII数字,数值为0—2147483647 按整数比较,02与2相同;负数、小数、空值拒绝

因此001与1是不同编号,Ab与ab也是不同编号。这是明确选择,不是所有库存系统的通用规则。如果编号曾被表格软件转换为数值并丢掉前导零,工具无法从“1”推回原来是001还是0001;应回到源系统按文本重新导出。

中文编号、带空格编号或合法负库存需要另定契约。本工具会拒绝它们,而不是擅自“清洗正确”。它也不是旧CSV预检包的直接后续接口:旧包包含名称、单价及其他字段,本包只比较两份sku、stock快照,转换格式和业务范围要显式决定。

把可复查的报告保存到新路径

python3 reconcile.py fixtures/left.csv fixtures/right.csv --output report.json
python3 -m json.tool report.json

报告的rows按SKU排序,每行同时保留left、right和status;summary给出四组数量。sources记录两份实际读取字节的SHA-256、字节数和记录数,便于确认讨论的是哪两份文件。

测试把右表重新排序后,语义结果rows保持一致,原始字节摘要则改变。这正是两种证据的分工:排序不该改变按键核对的结论,摘要仍要如实标识文件字节。摘要不是签名,也不能证明导出数据真实。

输出先写到同一目标目录的临时文件,完成后以硬链接创建最终路径;已有路径不会被替换。本次重复使用report.json得到退出码1,旧文件字节保持不变;模拟发布失败时,最终文件没有出现,临时名也已清理。

退出码 本工具含义
0 比较完成;有--output时也已创建新报告
2 输入契约或命令参数错误,检查错误信息
1 文件读取/发布等I/O错误,例如目标已存在

发现差异本身不返回非零:合法数据完全可能有数量变化,调用方应检查summary,而不能把退出0解读成“两份库存一致”。如果使用shell的>重定向,覆盖由shell执行,不受--output保护;再次保存时请选择新的输出文件名。

发布步骤要求可信的本地目录及硬链接支持;本轮没有验证Windows、网络盘、掉电或并发写入。强杀进程可能留下临时文件,不承诺所有异常情况下都自动清理。原始两份输入在正常命令行测试后逐字保持不变。

报告中的“仅左侧”还不能决定删除

本例的B-002只出现在左表,可能是两次导出过滤条件不同、导出时间不同,或确实缺失。工具只证明它未出现在这份右侧文件里,不知道哪一系统权威,也不会据此删除商品或覆盖库存。

真实核对前,应确认两份导出属于同一仓库、同一库存口径和可比较的时间窗口。脚本先后读取两个文件,并不构成跨系统的同时快照;正在被其他程序改写的文件应先固定为受控副本。总数量接近也不能替代逐键核对,更不能据此判断实际仓库没有差错。

8项测试覆盖真实命令行报告与输入保持、乱序、两侧重复键、非法输入子案例、BOM与数量解释、旧报告保护、发布失败清理和资源上限分支。上限测试降低阈值触发,不是万行压测。正文的四组差异和重复键四条连接结果均来自本次运行,样本全为合成数据。

完整包采用MIT许可,SHA-256:0a4d5803f66399904c5bd80ce4a0358ddbe7bf64cf93bd16ae03915a71795a95。封面复用本站浅色表格转结构化数据示意,不是Excel或库存系统实测截图;正文表格对应本次输入与输出。

一手资料:Python csv读取规则、SQLite SELECT连接语义。相关旧稿:zip的长度检查边界、单份CSV的导入预检、核对后写入数据库时的计划与事务。

资料查阅与编辑日期:2026年11月8日。作者:玖柒。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享