简介这是一份使用Access数据库编写的进销存系统覆盖采购、销售与库存管理适合中小企业管理人员以及希望掌握桌面数据库开发的读者。压缩包共十三个文件整体大小约381KB文件类型涵盖数据库文件、编译运行版、界面控件、说明文档、批处理脚本和帮助页面等其中数据库文件保存供应商、商品、订单等核心数据编译版可直接运行控件用于增强操作体验文档辅助安装配置。目前该资源已有1524人学习下载口碑良好。系统功能包括供应商与商品维护、采购订单录入、销售开单、库存自动扣减与低库存提醒并利用报表和VBA宏实现月度统计与自动化业务规则。下载后既可快速部署试用也能通过修改表结构、窗体设计和模块代码进行二次开发是学习Access查询、窗体、宏及VBA综合应用的完整示例。1. 用 ACCESS 写进销存系统为什么到现在还有公司在用先给结论用 ACCESS 写一套进销存系统并不是技术落后而是在“没有专职 IT、服务器预算有限、业务规模不大但数据要规范”的场景下最容易被低估的正确解法。我见过不少小批发商和单门店SKU 已经上千还在用 Excel 保存入库出库月底对账靠人工筛一遍慢而且容易错。换成 Access 之后窗体录入、库存自动计算、报表打印都能在几天内跑起来。这套方案适合三类人一是想把 Excel 表格升级成“有数据库约束”的系统但不想上重型 ERP 的内部管理者二是接了内部工具开发需求需要快速交付并方便业务方自己维护的一线开发者三是想学“用 Access 做企业小程序”这条技术路线的学生或转行者。它解决的核心问题只有一个用最小成本让进货、销售、库存这三个环节的数据不再靠手工表格互相搬运。2. 开搭之前把进销存的数据骨架理清楚2.1 进销存的核心业务单据与数据流向进销存系统的本质是把“货品在仓库里的变化过程”记录成一张张单据再根据单据计算库存。绝大多数业务都可以归纳成三种动作采购入库向供应商进货库存增加销售出库卖给客户库存减少盘点/调拨/报废修正账面库存与实际库存的偏差这三种动作对应三种单据。每张单据又分成“单据头”和“单据行”两部分单据头记录“谁、什么时候、哪个供应商/客户、整单备注”单据行记录“具体哪些货品、数量、单价、金额”。这是进销存最经典的“头行结构”也是后续所有窗体和报表的基础。用 Access 写进销存最容易犯的第一个错误就是把“库存”当成一张静态表直接手工改。比如业务人员发现库存不对就打开库存表把数字改掉这样系统里没有任何记录能解释这个变化。正确的做法是库存表永远由程序根据入库单和出库单计算出来不允许人工直接修改库存数字。这一条原则比任何技术细节都重要。2.2 用 Access 建表的理由与字段设计Access 适合做进销存核心原因有三个自带图形化表设计器建表不用写脚本窗体设计器能拖出界面业务人员看着像 Excel学习成本低内置 VBA保存、校验、库存计算都能写在按钮事件里不需要单独部署运行环境。相比直接上 MySQL、Java、Web 前端Access 的交付物就是一个文件复制、备份、迁移都简单得多。我一般会在 Access 里建六张基础表商品档案、供应商、客户、采购入库单头表、采购入库明细行表、销售出库单头表、销售出库明细行表、库存表。注意这里不是六张实际上是八张因为头行各拆两张。字段设计上有一个必须养成的习惯数量用数字类型金额用货币类型日期用日期/时间类型不要图省事全用文本。下面是一套我实际采用过的字段设计列出来供参考表名关键字段类型作用商品档案商品编号、品名、规格、单位、默认进价、默认售价、停用标记文本/数字/货币货品主数据供应商供应商编号、名称、联系人、电话文本采购抬头客户客户编号、名称、联系人、电话文本销售抬头采购入库头表入库单号、入库日期、供应商编号、操作员、备注文本/日期时间单据主表采购入库明细明细序号、入库单号、商品编号、数量、进价、金额数字/货币单据行表销售出库头表出库单号、出库日期、客户编号、操作员、备注文本/日期时间单据主表销售出库明细明细序号、出库单号、商品编号、数量、售价、金额数字/货币单据行表库存表商品编号、当前库存、成本均价、最后入库日期数字/货币/日期库存快照字段命名尽量用英文或拼音不要用中文因为中文名在 VBA 里虽然能用但写 SQL 和代码时容易混淆而且某些老版本 Access 对中文列名的兼容性不够好。编号字段有讲究入库单号和出库单号建议自己做规则生成比如“RK20250617001”不要依赖 Access 的自动编号因为自动编号在删过记录后会跳号做单据可追溯性不理想。2.3 表关系与参照完整性在 Access 的“数据库工具 → 关系”里把这些表关联起来采购入库明细的“入库单号”关联采购入库头表的“入库单号”采购入库明细的“商品编号”关联商品档案的“商品编号”销售出库明细的“出库单号”关联销售出库头表的“出库单号”销售出库明细的“商品编号”也关联商品档案的“商品编号”。设置关系的时候两个选项要特别注意“实施参照完整性”通常要勾选它保证明细表里不会出现头表没有的单据号“级联删除相关记录”不要轻易勾选一旦误删头表明细会一起没了库存记录也会失去来源单据。这里应该改成“禁止删除被明细引用的单头”业务上宁可留空单也不能直接删历史单据。很多人不理解为什么库存表不直接关联商品档案其实库存表是聚合结果它和商品档案是 1 对 1 的关系但它的值由程序写入不由用户手工维护。把库存表放在关系图里只是为了方便做查询不要给库存表设置“更新后刷新商品表”之类的级联操作否则会出现一边改商品编号、一边把库存清空的坑。3. 在 Access 里跑通第一版最小入库出库流程3.1 用建表 SQL 造出核心表虽然 Access 图形化建表很方便但为了可重复性和版本管理我更推荐把表结构写成 SQL在 Access 的“创建 → 查询设计 → 关闭显示表 → SQL 视图”里直接执行。下面是核心表的一段创建脚本CREATE TABLE tblProduct ( ProductID TEXT(20) PRIMARY KEY, ProductName TEXT(50) NOT NULL, Spec TEXT(30), Unit TEXT(10), DefaultCost CURRENCY, DefaultPrice CURRENCY, IsStopped YESNO DEFAULT 0 ); CREATE TABLE tblStock ( ProductID TEXT(20) PRIMARY KEY, Qty DOUBLE DEFAULT 0, AvgCost CURRENCY DEFAULT 0, LastInDate DATETIME, CONSTRAINT FK_Stock_Product FOREIGN KEY (ProductID) REFERENCES tblProduct (ProductID) );说明一下ProductID 用文本是因为商品编号往往带字母前缀比如“SP001”文本主键在 Access 里性能足够Qty 用 DOUBLE 而不是长整型是因为有些商品按重量出库比如“公斤”或“米”双精度能保留小数而不会丢精度。金额统一用 CURRENCYAccess 的货币类型内部用整数存储避免了浮点误差这点比直接用数字类型更可靠。3.2 做一个主窗体加子窗体录入采购入库建完表之后最快跑通录入界面的方式是使用“创建 → 窗体向导”选择“采购入库头表”作为数据源再选择“采购入库明细”作为子窗体数据源。Access 会自动按表关系把头表和明细表的子窗体拼在一起这个操作不需要写一行代码。但自动生成的窗体默认允许用户直接浏览所有记录这在进销存里不安全。我会手动调整几个属性把“添加”和“编辑”设为允许“删除”设为否明细子窗体设为“连续窗体”这样一屏能看到多行商品录入数量字段设置有效性规则比如大于 0同时在“输入掩码”里限制只能输入数字。一个小技巧子窗体的商品编号字段不要用文本框换成“组合框”数据来源指向商品档案表显示品名和规格保存的是编号。这样录入时不用去记编码鼠标点选就行。组合框的设置路径是右键控件 → 更改为 组合框 → 行来源类型“表/查询” → 行来源“SELECT ProductID, ProductName, Spec FROM tblProduct WHERE IsStopped0”。这个改动会让录入效率提升一倍。3.3 库存计算的两种位置选择库存算在哪里是进销存设计的分水岭。常见做法有两种第一种叫查询聚合也就是不维护库存表每次要看库存时对全部入库单和出库单做 Sum 汇总算出当前库存第二种叫库存冗余也就是在保存单据的同时立刻更新库存表里的 Qty 字段。查询聚合的好处是逻辑绝对正确不会因为程序漏更新而算错代价是商品多、单据多的时候查询越来越慢。库存冗余的好处是查询快打开库存列表瞬间出数代价是需要保证“保存单据”和“更新库存”这两个动作永远同时完成否则中间断电、报错会造成账实不符。小规模进销存我建议直接用第二种同时配合日志表记录每次库存变更的来源单号方便排查。日志表结构很简单商品编号、变更数量、变更前库存、变更后库存、来源单号、变更时间每次加库存或减库存都插一行。这个日志表是后续排查“库存为什么不对”的关键后悔药一定不能省。4. 用 VBA 把库存逻辑固定下来保存即冲减4.1 在保存按钮里完成“下单 冲库存”的完整事务窗体界面搭好之后核心工作是把保存按钮的 VBA 代码写好。这段代码必须做到先校验单据头再逐行写入明细最后逐行更新库存。三个动作要么全部成功要么全部回滚不能让用户看到“单据保存成功但库存没变”的情况。Private Sub btnSave_Click() On Error GoTo ErrHandler Dim db As DAO.Database Dim rsDetail As DAO.Recordset Dim rsStock As DAO.Recordset Dim sBillNo As String Dim dQty As Double, dCost As Double Set db CurrentDb 开始事务保证单据和库存同生共死 db.Execute BEGIN TRANSACTION 生成单号前缀加日期加三位序号 sBillNo RK Format(Date, yyyymmdd) Format(DLookup(Count(*), tblPurchaseBill, BillDate# Format(Date, yyyy-mm-dd) #) 1, 000) 插入单据头 db.Execute INSERT INTO tblPurchaseBill (BillNo, BillDate, SupplierID, Operator, Note) VALUES ( sBillNo , # Format(Me.BillDate, yyyy-mm-dd) #, Me.SupplierID , Me.Operator , Me.Note ) 遍历子窗体每一行写明细并更新库存 Set rsDetail Me.frmDetail.Form.RecordsetClone rsDetail.MoveFirst Do While Not rsDetail.EOF dQty rsDetail!Qty dCost rsDetail!Cost db.Execute INSERT INTO tblPurchaseDetail (BillNo, ProductID, Qty, Cost, Amount) VALUES ( sBillNo , rsDetail!ProductID , dQty , dCost , (dQty * dCost) ) db.Execute UPDATE tblStock SET Qty Qty dQty , AvgCost dCost , LastInDate # Format(Date, yyyy-mm-dd) # WHERE ProductID rsDetail!ProductID db.Execute INSERT INTO tblStockLog (ProductID, ChangeQty, SourceBillNo, ChangeTime) VALUES ( rsDetail!ProductID , dQty , sBillNo , # Format(Now, yyyy-mm-dd hh:nn:ss) #) rsDetail.MoveNext Loop db.Execute COMMIT TRANSACTION MsgBox 入库单保存成功单号 sBillNo Me.Requery Exit Sub ErrHandler: db.Execute ROLLBACK TRANSACTION MsgBox 保存失败已回滚 Err.Description End Sub这一段代码是整套系统的核心。注意几个关键点外层用 BEGIN TRASACTION 和 COMMIT 包住所有写操作任何一行出错就回滚避免“部分写入”的状态记录集用 RecordsetClone 而不是直接操作 Me.frmDetail.Form.Recordset因为循环修改时直接操作界面记录集容易报“记录已被删除”错误单号生成用 DLookup 计数只适合并发量很低的场景如果多人同时开单需要换一种方式这个在第 5 章避坑里细说。4.2 出库时先判断库存是否充足出库逻辑与入库的区别在于先要判断库存够不够不够就给提示并终止保存。下面是出库保存代码中库存校验的部分Private Sub btnSellSave_Click() Dim rsCheck As DAO.Recordset Dim dStock As Double, dNeed As Double Set rsCheck CurrentDb.OpenRecordset(SELECT Qty FROM tblStock WHERE ProductID Me.ProductID ) If rsCheck.EOF Then MsgBox 该商品还没有库存档案请先做入库。 Exit Sub End If dStock rsCheck!Qty dNeed Me.Qty If dNeed 0 Then MsgBox 出库数量必须大于 0。 Exit Sub End If If dNeed dStock Then MsgBox 库存不足当前库存 dStock 本次需要 dNeed Exit Sub End If 库存够继续写入出库单并做库存扣减 ...与入库类似此处省略重复代码 End Sub这段代码的逻辑说明读库存时如果查不到记录说明商品没有初始化出库数量必须大于 0这是从源头拦住负数单据库存不足判断用的是大于号等于不算超卖。这里的库存检查与后续扣减不处于同一个事务严格来说在高并发下仍有超卖风险但 Access 的场景通常并发量个位数这个检查已经足够不必在这上面做过度设计。4.3 遇到错账用红字冲销单而不是物理删除新手最常见的翻车操作是发现录错了就打开明细表直接删记录再手工把库存改回去。这种做法的后果是库存是改回来了但历史单据缺失以后任何统计都少了一笔对账时完全解释不清为什么流水和实际库存对不上。正确的做法是“冲销单”如果入库单录错了不做删除而是新开一张出库单数量填写和原单相等的正数备注写明“冲销单号 RK20250617001”如果出库录错了新开一张入库单数量填原单数量备注写“冲销出库单 XS20250617003”。这样前后单据互相抵消库存回到正确值流水里每一笔都有迹可循。这个习惯一开始就要给业务人员说清楚系统里的数据只增不改尤其是库存相关的记录任何修改都通过新单据完成。宁可多录一张冲销单也不要去碰数据库里的旧记录这是进销存长久稳定运营的关键纪律。5. Access 进销存的避坑清单并发、路径、备份与版本5.1 网络盘直接放 .accdb数据库频繁损坏现象公司把 Access 文件放在局域网共享文件夹几个人同时录入用了一两个月某个用户打开时报“不可识别的数据库格式”或“无法打开应用程序”数据库报废。原因Access 是文件型数据库数据读写通过网络共享盘上的单个文件完成。网络瞬断、两台机器同时写同一页缓存、Windows 锁文件冲突都会导致文件损坏。这个问题在无线网络、云同步盘比如网盘实时同步目录上尤其严重因为同步盘可能在用户编辑过程中就把半成品文件同步到云端造成版本覆盖。解决最稳妥的做法是“拆分数据库”把数据表放到一台固定机器的共享目录中每台客户端放一个只含窗体、查询和代码的前端文件通过链接表连接后端数据。这样写文件的是一个相对稳定的网络路径且断开时只会影响单条操作不会一坏坏一整份。另外设置 Access 选项里的“默认打开模式”为“共享”并关闭“自动压缩”可以降低文件损坏概率。5.2 多人同时保存出现“写入冲突”弹窗现象两个用户同时编辑同一条商品资料后者保存时 Access 提示“记录已被其他用户更改”如果处理不当后保存的人会把前者的修改静默覆盖掉。原因Access 的默认并发控制是按记录进行的界面上的记录集是静态快照用户打开记录时读入内存保存时 Access 检查记录版本是否变化发现不一致就拒绝写入。解决三个方向。一是修改窗体属性里的“数据输入”为“是”让用户只能新增记录不能编辑旧记录旧记录修改通过专门的管理窗体做二是给关键表加上“修改时间”字段DATETIME保存时同时更新该字段并在前端校验是否是同一版本三是接受现状明确告诉业务人员这是保护措施不是 bug。对于进销存头表允许编辑明细表设置为只能追加是最省事的组合。5.3 库存对不上账流水查得出原因但不知道从哪开始错现象某商品账面库存 300实际盘点 260查单据流水发现累计入库出库都对得上但就是差了 40。原因最典型的是有人直接修改过库存表或者做过物理删除。另一种隐蔽情况是某张单据保存时事务没有完整提交也就是代码里漏了 BEGIN TRANSACTION导致明细写入了、库存没更新或者反过来。Access 不会主动报错。解决检查库存日志表 tblStockLog逐条按时间顺序核对变更记录与来源单号如果日志表没记录说明这套系统缺了最核心的审计表没有后悔药只能靠手工盘点基准值反推。所以我在第 3 章反复强调库存日志表一定要有它平时不显眼真出事的时候就是唯一的救命稻草。5.4 .mdb 还是 .accdb位数和并发容量怎么选现象老系统用 .mdb 格式新装 Access 64 位打开正常但编译 VBA 时报错或者某个控件提示“不能在此版本中运行”。原因.mdb 是 Access 2003 之前的主流格式.accdb 是 2007 之后的默认格式。两者底层引擎不同.accdb 支持多值字段、附件、更长的文本但老格式在 64 位 Access 下部分功能受限。还有一个隐蔽问题Access 的 VBA 代码里如果用了 WinAPI 声明同时装 Office 32 位和 64 位会有兼容问题。解决新项目一律用 .accdb并且明确团队统一安装 32 位 Office。32 位不是因为它比 64 位好而是绝大多数成熟的 Access 控件比如日期选择、树形视图只提供 32 位版本64 位环境能用的第三方控件极少。真遇到需要处理超大数据的场景Access 本身就不是最优选择应该考虑迁移。5.5 备份策略文件复制不是备份可恢复的备份才是现象数据库文件还在但某个用户的误操作把几千条商品资料全部清空保存后文件正常关闭第二天才发现。此时复制文件已经没有意义因为备份也被覆盖了。原因进销存数据每天都在变单纯依赖手动复制文件备份频率和灾后恢复的可用性无法保证。很多小公司出事后翻出备份文件才发现备份是三个月前的。解决写一个简单的 VBS 或批处理脚本每天下班后用命令行把后端 .accdb 复制到一个带日期的文件夹然后通过任务计划程序定时执行。保留最近 30 天的备份至少保留每月一个归档备份。恢复时先复制最近一个正常备份再引导用户把今天丢失的少量新单据手工补录而不是试图从损坏文件中抢救数据。备份这件事规则只有一个定时、自动、异地三条缺一条都不算安全。6. 往工程化走一步拆分前后端数据库以及什么时候该迁移6.1 用链接表把数据与程序分离实际操作步骤并不复杂新建一个空白数据库命名为“后端_Data.accdb”把 tblProduct、tblStock 等所有数据表全部导入或剪切过去原数据库保留所有窗体、查询、报表和 VBA 代码删除本地表后通过“外部数据 → 新数据源 → 从数据库 → Access”建立链接表。链接表打开时走网络路径性能上略有下降但每台电脑都用自己的前端文件并发冲突大幅减少数据库文件损坏的概率会降到很低。链接表有一个注意事项链接表的路径是绝对路径如果服务器共享目录改了名字所有前端文件都要重新设置链接。我一般会在前端放一个“重新连接”按钮用 VBA 遍历 TableDefs 重建链接这样业务人员机器换了路径自己就能修复不用每次都找我。6.2 用 Access 报表做打印版单据进销存除了录入和数据还有一个刚需是打印采购单和销售单。Access 报表的设计核心只有两点一是数据源绑定到“单据头 单据明细”的查询报表头显示单据号和客户/供应商信息明细区域绑定子报表二是在报表的页面页脚放总金额和签收栏用表达式 Sum(金额) 汇总整单金额。有个小坑要注意Access 报表的默认纸张是 A4如果是针式打印机打三联单需要把纸张尺寸自定义成宽 24 厘米、高 14 厘米并将左右边距设为 0。如果打印出来总是超出纸宽检查“报表设计 → 页面设置 → 列 → 宽度”避免报表主体宽度超过纸张可用宽度。这一项不调好打印出来永远是斜的错位的用户会直接对系统失去信任。6.3 什么时候该考虑迁移到 SQL ServerAccess 进销存的上限取决于三个指标单表数据量超过几十万行、同时在线操作人数超过 10 人、有严格的权限审计要求。这三点满足任何一个Access 都开始吃力。尤其是“审计”这个需求Access 的日志只能靠程序自己写不像数据库能开完整的事务日志和权限日志。但迁移不是说一下子换系统。常见做法是把后端从 .accdb 换成 SQL Server Express免费版前端还是 Access只是链接表改为 ODBC 连接。这样窗体、报表、VBA 逻辑全部保留只换数据引擎业务人员几乎没有感知。再往后如果流程复杂到需要工作流引擎、移动端审批那时候才值得考虑正式 ERP。我自己做过好几次类似项目最大的教训是工具只是载体进销存能不能长期用下去取决于一开始有没有把“单据驱动库存、只增不改、留有日志”这三条原则传达到位。技术上的坑都是能补的业务习惯一旦坏了系统再漂亮也是摆设。希望这篇笔记能帮你把第一版跑通也帮你把未来可能踩的坑提前填上。本文还有配套的精品资源点击获取