2026/10/15 4:26:06

Access VBA初级实战:从单元格思维到记录集与批量处理

Access VBA初级实战:从单元格思维到记录集与批量处理 简介《Access VBA教程初级篇》是一份面向Access初学者的PDF电子书主要帮助零基础或入门级用户掌握使用VBA进行Access编程的方法以实现自动化数据处理、功能增强和定制化用户界面适合职场办公人员、数据库初学者自学使用。资源包仅含一个PDF文件体积1.76MB轻巧便携方便随时查阅。教程内容系统全面涵盖了VBA编辑器环境与项目结构、变量声明、数据类型、常量与运算符、If和循环等流程控制语句也介绍了Access对象模型、事件驱动编程、窗体控件属性调整、过程和函数定义、On Error错误处理、Recordset记录集操作、SQL查询语句以及宏与VBA的相互调用等核心知识点并配有实例演示便于理解。同时延伸到批量数据处理、定时备份、数据导入导出等实用自动化任务。目前已有378人学习学完后可具备扎实的Access VBA入门能力为后续开发复杂应用打好基础附录还整理了孙兴华2020年系列课程导航便于拓展Excel、PowerBI、Python等技能。1. Access VBA 初级篇最该先想明白的一件事Access VBA 一向被低估。刚接手一个库存核对任务时我用 Excel 写了半个下午的循环公式反复刷新透视表还是卡得不行后来换成 Access 里的 VBA 批处理几十秒就把十几万条对账记录跑完了。也就是那一刻我才明白Access VBA 不是给人写报表脚本用的它是把“查询”“界面”和“数据处理逻辑”焊在一起的胶水层。这篇笔记想讲的就是初级篇里真正值得你先搞清楚的东西它适合谁、能解决什么问题、以及为什么你以前觉得它不如 Excel 顺手其实多半是没摸对门路。如果你已经会一点 Excel VBA又经常被多表关联、重复数据清洗和多人共用数据这种需求压得头疼那 Access VBA 就是你下一个该补的工具。它和 Excel VBA 语法同源但对象模型、触发方式和数据操作思路完全两回事把这层差异看透后面所有代码都不难。2. Access VBA 的三个基本盘对象模型、事件驱动和两种数据引擎2.1 Access VBA 与 Excel VBA 的本质差异从“单元格”到“记录集”的思维切换许多从 Excel 转过来的人第一反应是在 Access 里找“单元格”然后发现找不到。这个方向本身就错了。Access 的核心不是网格是表和表之间的关系。VBA 操纵的也不是单元格坐标而是 Recordset——一个逻辑上的结果集它可能来自一张表也可能来自一条跨五张表的关联查询。Excel 里你写Range(A1).Value 2Access 里你写的是rs!库存数量 2然后rs.Update。前者改的是一个坐标点后者改的是当前记录的一个字段改完还要主动提交。这个切换代表的是两种数据模型思维Excel 面向二维区域Access 面向关系和游标。所以初学 Access VBA 的第一课我会让你先忘掉单元格学会理解三件事当前数据库对象CurrentDb所有表、查询、记录集都从它身上拿记录集DAO.Recordset数据在代码里的“当前行”容器配合MoveNext、EOF这类游标操作移动DoCmd对象负责打开窗体、运行查询、关闭界面等一切“界面级”动作。2.2 打开 VBA 编辑器与第一个可运行的过程模块到底插在哪进入 Access 后按AltF11打开 VBA 编辑器左侧工程资源管理器能看到当前数据库的各类对象。“模块”才是放代码的地方双击模块名进入代码窗口。下面是最小可运行示例作用是把当前日期写进一张叫日志表的表里。Sub 写日志() Dim db As DAO.Database Dim rs As DAO.Recordset Set db CurrentDb Set rs db.OpenRecordset(日志表, dbOpenTable) rs.AddNew rs!操作时间 Date rs!说明 示例日志 rs.Update rs.Close Set rs Nothing End Sub说明一点dbOpenTable只适用于本地表这种简单场景。如果是查询结果或者来自多表的记录集你得换用dbOpenDynaset类型后面会细讲。AddNew之后字段赋值再Update提交这套顺序是新增记录的标准动作顺序不能乱。模块插入的位置也有讲究如果这个 Sub 只在某个窗体里用属于“窗体类模块”如果是全局工具函数应该放在“标准模块”里。初级常见的错误是全都塞进窗体模块等到别处调用时才发现过程名不可见。标准模块在任何地方都能直接调用窗体模块里的过程反过来不行。2.3 宏、查询和 VBA 各自的边界什么时候别写代码Access 自带“宏”功能和“查询设计器”这两样东西能覆盖很多简单需求。一个纯入门者可能连代码都不用写用查询就能完成筛选和统计。问题是查询能干的事有边界循环、条件分支、逐行处理、文件操作、批量更新多条规则不一的记录这些才是 VBA 的统治区。我见过一个典型场景把一张销售明细表里的客户等级按消费额打标规则有四五条用查询设计器做出来是一大串嵌套IIf又长又难维护。用 VBA 读记录集循环判断代码短一半逻辑也清楚得多。反过来也有过度写代码的一个简单的更新查询非要用 VBA 循环几万条记录逐行 Update结果跑几分钟。这类批量更新应该直接写一条 Update SQL或者用db.Execute执行连遍历都不需要。判断标准就一句话能交给 SQL 和查询做的事不写循环循环只解决 SQL 表达不了的分支逻辑。3. 操作数据用 DAO Recordset 与 SQL 组合出第一个批量处理代码3.1 DAO 和 ADO 怎么选初级优先 DAO 的四个理由到了 Access 里你会在代码里碰到两套数据访问接口DAO 和 ADO。很多教程两种都讲初学者直接懵。初级项目我一律建议优先 DAO原因很实在DAO 是 Access 的“原配”老项目、旧库、示例代码绝大多数都是 DAO 写法跟着学不容易被版本差异卡住DAO 对 Access 表、查询、关系等对象支持更直接性能在这个场景下也更稳ADO 更通用适合连接外部数据库但对 Access 初级项目属于杀鸡用牛刀引用和兼容性坑少DAO 基本上打开 VBA 编辑器就能用不用额外装东西。如果要确认工程里 DAO 引用是否可用在 VBA 编辑器里点菜单“工具 → 引用”找包含DAO字样的库名。新版 Office 里即使名称里不带版本号只要类型库描述是 DAO 就行。如果引用旁边出现“丢失”字样说明代码里某处引用了一个当前环境没有的库最常见的处理方式是取消勾选再重新勾选。3.2 用代码批量更新记录的完整骨架遍历与条件分支批量打标的场景很能说明 DAO 的写法。假设有一张客户表字段有客户ID、年消费额、客户等级。下面是按年消费额分级的实现Sub 更新客户等级() Dim db As DAO.Database Dim rs As DAO.Recordset Set db CurrentDb Set rs db.OpenRecordset(客户表, dbOpenDynaset) Do While Not rs.EOF Select Case rs!年消费额 Case Is 100000 rs!客户等级 A Case Is 50000 rs!客户等级 B Case Is 10000 rs!客户等级 C Case Else rs!客户等级 D End Select rs.Update rs.MoveNext Loop rs.Close Set rs Nothing End Sub这个例子值得注意的点有三处。一是OpenRecordset的第二个参数dbOpenDynaset表示这是一个可编辑的动态集能改数据二是每次给字段赋值之后必须调rs.Update否则修改只在内存里游标移动后就被丢弃三是MoveNext必须写在循环体末尾如果漏掉程序会死循环在第一条记录上。EOF是“已越过最后一条记录”的标志游标越界了才为真。这里有一个想提醒你的事rs.Update在Select Case的每个分支之后都会执行看起来有些冗余但它是必要的。DAO 里Edit和Update总是成对出现你可以在循环前用一次rs.Edit但不同版本对这个顺序要求很严格老老实实每次修改后跟随Update最稳。3.3 用 Execute 执行批量更新几十万行数据别走循环循环适合逻辑复杂、逐行判断的场景。如果只是把满足某个条件的记录统一改掉比如把所有 2023 年之前的订单标记为“已归档”根本不需要 RecordsetDim db As DAO.Database Set db CurrentDb db.Execute UPDATE 订单表 SET 归档状态 已归档 WHERE 订单日期 #2023-01-01#, dbFailOnError这里dbFailOnError表示执行出错时抛出异常而不是静默失败。Execute返回受影响行数在批量核对报文中很有用比如判断是否有记录被更新。注意 Access 里的日期常量写法要用#包裹而不是字符串的单引号这是 Access SQL 和别家 SQL 语法差异之一后面避坑章会再提。如果更新多条不同的规则比如不同地区对应不同的折扣率这种情况下你可以先建一张“规则表”再用一条多表更新 SQL 完成而不要写循环逐行 Update。多表更新在 Access 的 SQL 语法里不直观但在查询设计器里通过“更新查询”两分钟能搭出来VBA 里用db.Execute执行同样的 SQL 即可。3.4 参数化查询别用字符串拼接 SQL 的值初级教程经常手把手教字符串拼接WHERE 客户ID Me!客户ID这种方式一旦字段值是文本或日期引号嵌套会写到你怀疑人生而且还有注入风险。虽然 Access 单机场景注入威胁不大但文本值里的单引号会把你的 SQL 直接搞崩这是最实际的坑。我一般会改用查询参数。先建一个名为查客户订单的查询SQL 写成SELECT * FROM 订单表 WHERE 客户ID 客户参数;注意客户参数不是字段名是参数名。在 VBA 里用QueryDef传参执行Dim qdf As DAO.QueryDef Dim db As DAO.Database Set db CurrentDb Set qdf db.QueryDefs(查客户订单) qdf.Parameters(客户参数) Me!客户ID Set rs qdf.OpenRecordset(dbOpenDynaset)参数化不仅绕开了引号地狱还有一个额外好处如果这个查询要在循环里反复执行比如根据多个客户ID逐个查订单明细参数化的执行性能通常比拼接 SQL 再重新解析要好。这种写法在初级篇里稍微超前一点但属于早学会早受益的那类技巧。4. 让按钮真正干活窗体事件与 DoCmd 命令的配合方式4.1 为什么初级项目离不开窗体事件Access 不是用来自动化后台的Excel VBA 最常见的是“打开工作簿自动运行宏”Access 更常见的则是“用户在窗体上点按钮触发动作”。这决定了初级 Access VBA 的重心是事件驱动而不是启动即执行。一个没有窗体的 Access 数据库基本只是查询和表的堆砌但配合窗体之后它才像真正的应用用户选择条件、点按钮、看到结果。按钮的单击事件叫Click在窗体设计视图中右键按钮 → “事件生成器” → “代码生成器”就能在对应的事件过程里写代码。下面的例子是一个查询按钮用户在窗体上输入月份点“统计”在子窗体里显示该月的订单汇总。Private Sub 统计按钮_Click() Dim db As DAO.Database Dim rs As DAO.Recordset Dim sql As String Set db CurrentDb sql SELECT 产品名称, Sum(数量) AS 总数量 FROM 订单明细 _ WHERE 订购日期 # Format(Me!开始日期, yyyy-mm-dd) # _ AND 订购日期 # Format(DateAdd(m, 1, Me!开始日期), yyyy-mm-dd) # _ GROUP BY 产品名称 Set rs db.OpenRecordset(sql, dbOpenDynaset) Set 子窗体.Form.Recordset rs End Sub这里Me表示当前窗体自身Me!开始日期是窗体上那个输入框的当前值。Format(日期, yyyy-mm-dd)是为了保证日期文本在 SQL 拼接时格式正确避免因 Windows 地区日期格式不同而产生的“日期无法识别”错误。子窗体.Form.Recordset rs这句是把查询结果直接塞给子窗体比先建查询再设RecordSource灵活得多适合临时性统计。窗口期的注意点在DateAdd(m, 1, ...)取“下个月同一天”来构造结束边界这样能包住整个月。这种写法比“当月最后一天”更省事又不怕月末大小月。4.2 DoCmd 命令的正确打开方式打开窗体、关闭窗体、刷新与提示窗体之间跳转是 Access 应用的常态。从一个列表窗体点某条记录跳到详情窗体靠的是DoCmd.OpenFormDoCmd.OpenForm 客户详情, , , 客户ID Me!客户ID第三个参数留空表示不指定过滤条件第四个参数填的是 WHERE 条件这里直接传了字符串。如果客户ID是文本类型记得要写成客户ID Me!客户ID 单引号不能省。这个引号问题初级几乎必错错了之后窗体打开会弹出“输入参数值”对话框原因就是条件里被当成了参数而不是字段。DoCmd里另一个常见命令是关闭窗体并确认保存If MsgBox(确定要关闭并保存吗, vbYesNo vbQuestion) vbYes Then DoCmd.Save acForm, 客户详情 DoCmd.Close acForm, 客户详情 End IfMsgBox返回值只有两个vbYes或vbNo判断时优先判断“是”的分支而不是先判断“否”。很多新手反过来写遇到用户点了右上角红叉关闭对话框时返回值变成vbNo逻辑就乱了。DoCmd和直接操作对象是两条平行路线DoCmd.OpenForm是让 Access 执行一个界面动作而Form_客户详情.Visible True是直接操作窗体对象。初级阶段统一用DoCmd就好因为它帮你处理了正常打开流程里的一系列系统状态。4.3 窗体加载、更新与关闭的触发顺序别在 Load 里操作还没出现的控件事件的触发顺序属于“不出错就永远不会注意”的知识但一旦出问题排查起来很耗时间。以订单编辑窗体为例完整的一次“打开 → 编辑 → 关闭”经历的事件依次是Open→Load→Current→Activate然后编辑字段触发BeforeUpdate→AfterUpdate关闭时依次触发Unload→Deactivate→Close。最容易犯的错是在Load事件里访问还没加载完成的控件。Access 的窗体加载顺序是先建窗体实例再填充数据源Load时数据还没绑定到控件上。此时读Me!订单日期得到的值可能是空或旧缓存。要把初始化和默认值设置放在Current事件里这个事件在每次“移动到某条记录”时都会触发才是数据稳定可读的状态。我习惯这样安排Load里只做窗体的外观初始化比如标题、按钮可用性Current里做数据相关操作比如根据当条记录状态改写按钮文本BeforeUpdate里做校验比如日期不能为空、数量必须大于零。三者职责分清楚窗体上的逻辑就不容易乱成一锅粥。5. Access VBA 初学避坑新手最容易踩的 5 个坑与排障路径5.1 运行代码时弹出“输入参数值”对话框怎么找都找不到原因现象执行查询或代码时Access 弹出一个“输入参数值”的输入框你点取消代码就报错。原因SQL 语句里有一个既不是字段名也不是函数名的“词”Access 把它当成了参数。绝大多数情况是字段名拼错、字段名包含空格却没用方括号、或者字段在查询里不存在。这类问题在查询设计器里不会出现因为设计器会直接提示未知字段但 VBA 里 SQL 是字符串Access 只能在运行时解析了才暴雷。解决在报错的代码行加断点运行到停住后按CtrlG打开立即窗口输入?sql回车把你拼出来的 SQL 完整打印出来。然后肉眼检查每个单词该用[字段名]的有没有漏掉方括号该用引号包文本值的有没有漏引号。一般情况下问题都在这一串打印出来的文本里一眼就能看到。5.2 Null 和空字符串混为一谈导致时间和日期判断全部失效现象记录里某字段看起来是空的用If Me!备注 Then判断却总不成立或者反过来明明有内容却被判成空。原因Access 里Null和空字符串是两回事。字段没有输入任何值时为Null输入了内容再删光则是空字符串。直接用等号比较Null永远得不到 True必须用IsNull()函数判断。解决涉及空的判断一律写成If IsNull(Me!备注) Or Me!备注 Then 处理空值 End If如果只是做个通用判断可以改用Nz()函数把Null转成指定值再比较If Nz(Me!备注, ) ThenNz(字段, 默认值)是 Access VBA 里最常用的防空函数它把Null替换成默认值这样后续拼接 SQL、字符串判断都省心很多。数值字段也同理Nz(数量, 0)可以避免把空值带入计算导致结果为Null而Null参与任何算术运算结果都是Null这是连环坑。5.3 循环更新记录集后 Access 卡死文件被锁别人打不开数据库现象一段遍历记录集的代码跑完后数据库变得不流畅关闭 Access 后ACCDB文件被锁定其他机器访问不了。原因Recordset对象没有被关闭游标一直占着资源更常见的是代码中途出错提前退出rs.Close和Set rs Nothing根本没执行到。Access 桌面数据库在多人共用时未释放的记录集锁会导致文件级锁定别人以独占方式打不开。解决养成写清理代码的习惯而且不是写在流程末尾是写在错误处理里Sub 修改记录() Dim db As DAO.Database Dim rs As DAO.Recordset Set db CurrentDb Set rs db.OpenRecordset(订单表, dbOpenDynaset) On Error GoTo 出错处理 中间是对 rs 的各种操作 rs.Close 退场: Set rs Nothing Set db Nothing Exit Sub 出错处理: MsgBox 发生错误 Err.Description Resume 退场 End Sub这段代码的精髓在于无论正常走完还是中途出错都会跑到退场:标签统一释放对象。Resume 退场在错误处理里负责把流程跳到清理段。对象释放顺序也有讲究先关rs再释放rs最后释放db。如果你发现数据库文件已经被锁让别人全部关闭那个前端文件后把ACCDB复制出来在新位置打开再保存一次通常可以解锁但治本办法还是让每个过程都有完整的Close Nothing。5.4 DoCmd.SetWarnings False 关掉提示后忘了恢复导致后续操作异常现象某段代码执行后后续手动删除记录、更新查询时 Access 不再弹出确认提示了而且有些操作直接“静默”执行让人心里发毛。原因代码里执行了DoCmd.SetWarnings False关闭系统警告但退出过程时没有恢复成True。这个设置是全局的不会随过程结束自动复位。解决DoCmd.SetWarnings的用法没有单次调用那么简单我建议只在必须屏蔽提示的语句前关闭执行完立刻恢复DoCmd.SetWarnings False db.Execute DELETE FROM 临时表, dbFailOnError DoCmd.SetWarnings True如果担心代码中途出错导致警告状态没恢复可以把恢复也放进错误处理的退场段。另外一个替代方案是凡是单条 SQL 能做的事用db.Execute而不是DoCmd.RunSQL执行db.Execute本身不会弹确认框完全不需要碰警告开关。这条经验能帮你省掉很多“为什么 Access 不提示了”的疑惑。5.5 引用了“丢失”的库文件打开任意一个窗体都报编译错误现象整库代码无法运行VBA 编辑器里点“运行”提示“编译错误找不到工程或库”可能每一个窗体模块都报错。原因工程里勾选了一个当前机器不存在的 COM 组件引用比如某台电脑装了某个第三方控件数据库带过去了另一台电脑没有这个控件引用就悬空了。引用悬空时 VBA 的变量类型解析会失败代码完全编译不过。解决打开 VBA 编辑器 → “工具” → “引用”看列表底部有没有标着“丢失”的项。取消勾选丢失项或者重新勾选正确的条目。如果数据库本身不需要那个组件直接取消勾选后保存即可如果需要但目标机器没有那就要装依赖组件这类问题常出现在把开发环境的数据库直接丢给别的电脑使用时。防止这个问题的习惯是开发完成后把代码里用到的对象声明尽量限制在 Access/DAO 自带的引用范围内少依赖第三方 ActiveX 组件。初级项目完全可以做到零第三方依赖的纯 VBA 实现。6. 初级到进阶的验证法用调试习惯给代码上保险初级篇学到后半段最值得花的力气不是背更多函数而是把调试工具用熟。Access VBA 的调试三板斧断点、立即窗口、本地窗口。三者配合能让你面对一段不熟悉的代码时十分钟内搞清楚每条变量在干什么。断点用法是在代码行号左侧点击出现红点后运行到该行会暂停。此时按F8逐行执行观察每行代码执行完以后变量的变化。本地窗口视图 → 本地窗口会显示当前作用域里所有变量的值特别是rs!字段名这类当前记录的值这将直接帮你确认游标是不是停在了预期的记录上。另一个让我受益很久的习惯是在写代码前先把 SQL 放到查询设计器里跑通再粘回 VBA。这样做能提前暴露 90% 的字段名拼写和关联关系错误剩下的再去立即窗口里打印 SQL 排查。你可以把这种套路理解为“先让 SQL 独立可验证再让代码调用它”而不是 SQL 和代码一起写完再查错否则出错时你很难判断是查询写错还是 VBA 调用方式错。还有一条验证技巧专门用于批量更新这类“无返回值”的操作在执行更新前后各统计一次记录数确认影响的行数符合预期。比如批量设客户等级前先查SELECT Count(*) FROM 客户表 WHERE 年消费额 100000执行完再查一次两边数字能对得上说明更新逻辑没漏也没多。越是没有明显报错的操作越要主动设这种验证点。这个项目的实践也让我养成了一个戒不掉的毛病任何一次会修改多条数据的代码第一步都是先把目标表完整备份一份然后才敢放心跑。Access 没有事务日志给你兜底CtrlZ在数据表里也救不回已经被循环覆盖的值备份就是唯一的后悔药。初级篇能学到这一步后面的中高级内容基本就是往这套框架里填更多对象和技巧了。希望这篇笔记能帮你把 Access VBA 的门推开。哪怕只是从抄第一段代码开始跑通一次批量更新再琢磨细节这个工具就会从“看不懂的旧式技术”变成你手里最顺手的桌面数据处理武器。本文还有配套的精品资源点击获取