☰
Access进销存系统实战:从表设计到VBA库存自动计算
2026/10/12 20:32:40 网站建设 项目流程

简介:这是一份使用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 而不是长整型,是因为有些商品按重量出库,比如“公斤”或“米”,双精度能保留小数而不会丢精度。金额统一用 CURRENCY,Access 的货币类型内部用整数存储,避免了浮点误差,这点比直接用数字类型更可靠。

3.2 做一个主窗体加子窗体录入采购入库

建完表之后,最快跑通录入界面的方式,是使用“创建 → 窗体向导”,选择“采购入库头表”作为数据源,再选择“采购入库明细”作为子窗体数据源。Access 会自动按表关系把头表和明细表的子窗体拼在一起,这个操作不需要写一行代码。

但自动生成的窗体默认允许用户直接浏览所有记录,这在进销存里不安全。我会手动调整几个属性:把“添加”和“编辑”设为允许,“删除”设为否;明细子窗体设为“连续窗体”,这样一屏能看到多行商品录入;数量字段设置有效性规则,比如大于 0,同时在“输入掩码”里限制只能输入数字。

一个小技巧:子窗体的商品编号字段不要用文本框,换成“组合框”,数据来源指向商品档案表,显示品名和规格,保存的是编号。这样录入时不用去记编码,鼠标点选就行。组合框的设置路径是:右键控件 → 更改为 组合框 → 行来源类型“表/查询” → 行来源“SELECT ProductID, ProductName, Spec FROM tblProduct WHERE IsStopped=0”。这个改动会让录入效率提升一倍。

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 Server

Access 进销存的上限取决于三个指标:单表数据量超过几十万行、同时在线操作人数超过 10 人、有严格的权限审计要求。这三点满足任何一个,Access 都开始吃力。尤其是“审计”这个需求,Access 的日志只能靠程序自己写,不像数据库能开完整的事务日志和权限日志。

但迁移不是说一下子换系统。常见做法是把后端从 .accdb 换成 SQL Server Express(免费版),前端还是 Access,只是链接表改为 ODBC 连接。这样窗体、报表、VBA 逻辑全部保留,只换数据引擎,业务人员几乎没有感知。再往后,如果流程复杂到需要工作流引擎、移动端审批,那时候才值得考虑正式 ERP。

我自己做过好几次类似项目,最大的教训是:工具只是载体,进销存能不能长期用下去,取决于一开始有没有把“单据驱动库存、只增不改、留有日志”这三条原则传达到位。技术上的坑都是能补的,业务习惯一旦坏了,系统再漂亮也是摆设。希望这篇笔记能帮你把第一版跑通,也帮你把未来可能踩的坑提前填上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询