ARTICLE · 1139364
同样是 VBA,为什么 Excel 的代码搬到 Access 就不能用?
摘要: Access VBA 和 Excel VBA 用的是同一种语言,真正不同的是操作对象、数据组织和程序执行方式。从单元格与记录、工作表函数与 SQL,到窗体事件、空值、事务和部署,弄明白这些区别,才知道哪些代码能直接复用,哪些做法需要重新设计。
Hi,大家好!
学过 Excel VBA,再打开 Access 的 VBA 编辑器,会有一种很熟悉的感觉:还是模块、过程、变量,还是 If、For、MsgBox,连调试窗口都差不多。
于是很容易想到:既然都叫 VBA,把 Excel 的代码复制过来,改几个名字,不就能用了?
处理字符串、整理文件名的代码,确实可能直接拿来用。可一旦代码里出现 Range、Worksheet、ThisWorkbook,到了 Access 就不是这么回事了。
反过来也一样。Access 里的 CurrentDb、DoCmd、Forms,放进 Excel 的模块,Excel 不会自动明白它们是什么意思。
之前我写过 Excel UserForm 和 Access 窗体的区别,那篇主要说界面。这次把范围放到 VBA 本身:明明语法一样,为什么写程序的方式会差这么多?
我觉得先弄清这一点,比先背几十个 Access 函数更有用。否则很容易学会了语句,却还是拿操作单元格的习惯去操作数据库。

语言是一样的,Office 提供的对象不一样
先说结论:Access VBA 和 Excel VBA,并不是两门不同的编程语言。
变量声明、条件判断、循环、数组、自定义函数、类模块、错误处理,这些基本规则是共通的。你在 Excel 里学会的 Option Explicit、ByVal、ByRef、On Error GoTo,到了 Access 里仍然有用。
比如一个函数接收字符串,去掉多余空格,再返回整理后的结果。如果里面只使用 VBA 自带的字符串函数,没有操作工作表,也没有操作窗体,它通常可以放到两个程序的标准模块里使用。
真正的区别,出现在代码准备操作 Office 对象的时候。
Excel 提供工作簿、工作表、单元格、图表、数据透视表等对象。Access 提供数据库、表定义、查询、记录集、窗体、报表等对象。VBA 负责调用它们,但不会把一种对象自动变成另一种。
可以简单看一下两边常见的对象关系:
Excel:Application → Workbook → Worksheet → RangeAccess:Application → Forms → Form → ControlAccess 数据访问:DAO.Database → QueryDef / Recordset这里有个容易忽略的细节:同样写 Application,在 Excel 里通常指当前 Excel 应用程序,在 Access 里通常指当前 Access 应用程序。
所以,Excel 的 Application.WorksheetFunction 和 Access 的 Application.DoCmd,并不是同一个对象上换了两种写法。它们所属的宿主程序就不同。
我一般会先看一段代码到底依赖哪些对象,再判断能不能搬。只看它有没有 Dim、For,看不出移植难度。
下面放几组对照代码。为了方便自己试,先在测试文件里准备同样的数据,不要直接修改正式资料。
Excel 新建两张工作表:客户 表的 A 到 D 列依次为客户ID、客户名称、地区、业务员;订单 表的 A 到 C 列依次为订单ID、客户ID、金额。第一行都是标题,客户 ID 用不重复的整数。
Access 新建本地表 客户,字段为客户ID(数字,长整型,主键)、客户名称(短文本,100)、地区(短文本,50)、业务员(短文本,50);再建 订单 表,字段为订单ID(数字,长整型,主键)、客户ID(数字,长整型)、金额(货币)。
两边都录入这两位客户,其他列暂时可以不填:
订单表录入订单 1001 和 1002,客户 ID 都是 101,金额分别为 100 和 250。后面的汇总结果应为 350。
除专门注明的事件代码外,Excel 代码放在这个工作簿的标准模块,Access 代码放在数据库的标准模块。Access 示例需要 DAO 引用,现代 .accdb 通常已经有;若编译提示找不到 DAO.Database,到 VBA 编辑器的「工具 → 引用」检查 Access database engine Object Library。
Excel 经常找位置,Access 更需要找身份
Excel VBA 很多操作从一个位置开始:读取 B2,写入 D5,从第二行循环到最后一行。
这在整理报表、套打印模板时很自然。因为你最终需要的,可能就是某个数出现在某个单元格,标题合并在哪几列,金额保留几位小数。
但业务数据还有另一种要求:不管用户怎么排序,客户还是那个客户,订单还是那张订单。
假设客户清单的第 18 行原来是甲公司。用户按地区排了一次序,第 18 行变成了乙公司。程序如果仍然把「第 18 行」当成甲公司的身份,更新时就可能改错人。
Excel 也可以解决这个问题。给每条数据安排唯一编号,使用表格对象、按列名定位、按编号查找,就比到处写固定行列号稳妥得多。不是说 Excel 只能靠位置,只是很多初期的宏确实从位置写起。
Access 则应该从一开始就把这件事想清楚。客户表用客户 ID 识别客户,订单表用订单 ID 识别订单,明细通过外键指向对应的订单。
数据表显示的第几行,不是记录的身份。
查询没有明确的 ORDER BY,就不要依赖某条记录恰好排在前面。自动编号也不等于连续行号,删除记录后出现缺号是正常的,不应该为了看起来整齐就重编主键。
这个差别会影响代码里几乎所有的查找、修改和删除。你要问的从「我改哪一行」变成了「我改哪个 ID 对应的记录」。
对照一:按客户 ID 找名称
Excel 这里也按 ID 查,不写死第二行。先到 A 列查找编号,再读取同一行的 B 列:
Public Function DemoExcelCustomerName(ByVal customerId As Long) As String Dim sheet As Worksheet Dim matched AsRangeSet sheet = ThisWorkbook.Worksheets("客户")Set matched = sheet.Columns(1).Find(What:=customerId, _ After:=sheet.Cells(sheet.Rows.Count, 1), LookIn:=xlValues, _ LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _ MatchCase:=False, MatchByte:=False, SearchFormat:=False) If Not matched Is Nothing Then DemoExcelCustomerName = CStr(sheet.Cells(matched.Row, 2).Value2)End IfEndFunctionAccess 不需要先知道记录显示在哪一行,按主键查询即可。参数单独传入,不拼客户名称或其他用户文字:
Public Function DemoAccessCustomerName(ByVal customerId As Long) As String Dim database As DAO.Database Dim query As DAO.QueryDef Dim records As DAO.RecordsetSet database = CurrentDbSet query = database.CreateQueryDef("", _ "PARAMETERS pId Long; " & _ "SELECT [客户名称] FROM [客户] WHERE [客户ID]=[pId];") query.Parameters("pId") = customerIdSet records = query.OpenRecordset(dbOpenSnapshot) If Not records.EOF Then DemoAccessCustomerName = CStr(Nz(records.Fields("客户名称").Value, ""))End If records.Close query.CloseEndFunctionExcel 的立即窗口执行 ? DemoExcelCustomerName(101),Access 执行 ? DemoAccessCustomerName(101),都应返回「甲公司」。找不到时,这两个演示函数都返回空字符串;实际业务若要区分「未找到」和「名称为空」,应另外返回查找状态。
Excel 用的是 Worksheet、Range 和行号;Access 用的是 DAO.Database、QueryDef 和字段名。条件一样,拿到数据的路径不一样。
同样是修改数据,循环和 SQL 的分工不同
在 Excel 里,把一列符合条件的内容改成新值,常见做法是循环每一行,判断单元格内容,再写回结果。
这个思路搬到 Access,也能写出来:打开记录集,逐条判断,调用 Edit、Update,再移到下一条。
不过,能写出来,不代表每次都应该这样写。
比如把某个地区客户的业务员统一改成另一个人,如果条件和修改规则都很明确,Access 往往更适合用一条更新查询完成。统计每个客户的订单金额,也通常先考虑分组查询,而不是把所有订单逐条读出来,在 VBA 里自己累加。
数据库本来就提供筛选、连接、分组、更新这些能力。VBA 可以负责接收用户选择、传递参数、决定什么时候执行,再把查询结果交给窗体或报表。
当然,SQL 不是什么都能方便表达。逐条调用外部接口、生成独立文件、处理复杂的文本规则,这些工作仍然可能需要记录集和 VBA 循环。
我一般这样判断:如果操作可以描述成「满足这个条件的记录,统一做这件事」,先考虑 SQL;如果每条记录都要执行一个外部动作,再考虑逐条处理。
另一个需要改掉的习惯,是把用户输入直接拼进 SQL。客户名称里有单引号,日期受到区域格式影响,或者输入内容改变了原本的查询条件,都可能造成问题。能用参数查询,就让参数负责传值,不要自己到处补引号。
Excel VBA 也能通过 ADO 等方式执行 SQL,这不是 Access 专属的语法能力。但在 Access 项目里,查询往往就是程序的一部分,不只是偶尔调用的一段附加代码。
对照二:修改某个地区客户的业务员
下面会实际修改测试数据。Excel 从第二行开始,检查 C 列地区,再写 D 列业务员,返回处理的行数:
Public Function DemoExcelChangeSalesperson(ByVal region As String, _ ByVal salesperson As String) As Long Dim sheet As Worksheet Dim lastRow As Long Dim rowIndex As LongSet sheet = ThisWorkbook.Worksheets("客户") lastRow = sheet.Cells(sheet.Rows.Count, 1).End(xlUp).RowFor rowIndex =2To lastRow If CStr(sheet.Cells(rowIndex, 3).Value2) = region Then sheet.Cells(rowIndex, 4).Value2 = salesperson DemoExcelChangeSalesperson = DemoExcelChangeSalesperson +1End If Next rowIndexEndFunctionAccess 把条件和新值交给参数查询,一次执行更新,返回受影响的记录数:
PublicFunctionDemoAccessChangeSalesperson(ByVal region AsString, _ByVal salesperson AsString) AsLongDim database AsDAO.DatabaseDim query AsDAO.QueryDefSet database = CurrentDbSet query = database.CreateQueryDef("", _"PARAMETERS pRegion Text(50), pSalesperson Text(50); " & _"UPDATE [客户] SET [业务员]=[pSalesperson] " & _"WHERE [地区]=[pRegion];") query.Parameters("pRegion") = region query.Parameters("pSalesperson") = salesperson query.Execute dbFailOnErrorDemoAccessChangeSalesperson = query.RecordsAffected query.CloseEndFunction两边分别执行 ? DemoExcelChangeSalesperson("上海", "张华") 和 ? DemoAccessChangeSalesperson("上海", "张华")。按上面的测试数据,应返回 1,甲公司的业务员变成张华,北京的乙公司不变。
这里的行数或记录数表示按条件处理了多少条,不表示新旧值一定不同。dbFailOnError 用于报告执行失败,不能代替前面说的多条语句事务。这组代码只演示一次更新,也不自动撤销已经成功的操作。

Excel 有计算引擎,Access 有查询和数据规则
会 Excel VBA 的人,通常不会把所有计算都写在 VBA 里。
金额让工作表公式算,汇总交给数据透视表,VBA 负责填数据、刷新和导出。这种分工很合理。Excel 的公式、计算引擎和表格界面,本来就是它的主要能力。
不过,Excel 工作表函数、VBA 函数和 Access 函数,不能混成一套来理解。
例如,Excel 单元格里的 SUMIF、XLOOKUP,不代表 VBA 里存在同名的内置函数。Excel 可以提供相应的工作表函数接口,但 Access 的 Application 不会因为也支持 VBA,就拥有同一套接口。
Access 则经常在查询表达式里计算,在窗体或报表控件里显示结果,必要时再调用自定义 VBA 函数。
对照三:汇总一位客户的订单金额
Excel 调用工作表函数 SumIf,用 B 列客户 ID 筛选,汇总 C 列金额:
Public Function DemoExcelOrderTotal(ByVal customerId As Long) As Currency Dim sheet As WorksheetSet sheet = ThisWorkbook.Worksheets("订单") DemoExcelOrderTotal = CCur(Application.WorksheetFunction.SumIf( _ sheet.Columns(2), customerId, sheet.Columns(3)))EndFunctionAccess 可以使用域聚合函数 DSum。没有匹配记录或没有可汇总的金额时,结果可能是 Null,这里明确把展示结果换成 0:
Public Function DemoAccessOrderTotal(ByVal customerId As Long) As Currency Dim total As Variant total = DSum("[金额]", "订单", "[客户ID]=" & CStr(customerId)) DemoAccessOrderTotal = CCur(Nz(total, 0))EndFunction两边分别执行 ? DemoExcelOrderTotal(101) 和 ? DemoAccessOrderTotal(101),都应得到 350。这里只把没有金额的汇总结果显示为 0,没有改动订单表中的空值。
这里拼接的条件仅使用声明为 Long 的整数 ID,不是把任意用户文本拼进 SQL。文本和日期条件仍应按实际场景处理,复杂筛选优先考虑参数查询。
Currency 在 VBA 里保留四位小数。Excel 的工作表计算仍使用它自己的数值规则,转换返回类型并不意味着两边的计算引擎相同。大量客户一起汇总时,Access 通常应使用分组查询,不要在列表里为每个客户重复调用一次 DSum。
两边还有一个共同的提醒:计算结果看起来相同,不代表它们在业务上的保存要求相同。
产品当前单价可以查询出来,但已确认订单的成交单价,通常需要作为订单明细中的历史值保存。不能产品一改价,过去的订单金额也跟着重新计算。
这时候需要决定的,已经不是选哪个函数,而是哪一个值应该实时算,哪一个值应该在业务发生时保存下来。
Access 的字段类型、必需属性、有效性规则、索引和表关系,可以承担一部分数据约束。比如编号不允许重复,可以建立唯一索引;明细不能引用不存在的主记录,可以在适用的表关系上实施参照完整性。
这些约束并不是 VBA 自动附送的,需要开发者实际设计。Excel 也有数据验证等功能,但它和数据库的唯一索引、参照完整性不是同一种保障。
程序不一定从按钮开始,也不一定到按钮结束
Excel 宏给人的直观印象,往往是「点一下,运行一遍」:整理工作表、汇总文件、生成报表,完成后弹出提示。
但 Excel 也有事件。打开工作簿、修改单元格、切换工作表,都可以触发代码。不能把 Excel VBA 只理解成一串手动执行的宏。
Access 的业务窗体则更经常围绕记录和控件事件组织程序:打开窗体、切换当前记录、修改字段、准备保存、完成保存,都可能有不同的处理。
这里最容易照搬错的,是保存时机。
Excel UserForm 通常由你自己决定什么时候把文本框内容写到工作表。Access 绑定窗体不一样,控件修改可能已经形成当前记录的未保存更改,移动记录或关闭窗体等操作,也可能触发保存。
所以,在 Access 里做必填检查、限制某种状态下不能修改,不能只把判断放到一个名叫「保存」的按钮里。用户没有点这个按钮,也不一定意味着这条记录没有尝试保存。
涉及当前记录能不能保存的规则,往往应该考虑窗体的 BeforeUpdate 事件;某个字段自己的检查,则可能放到控件事件。哪些规则需要同时放到表这一层,还要看是否有其他入口写入数据。
绑定窗体并不适合所有场景。一次录完多张表、最后统一确认,或者需要完全自己控制提交时机的界面,也可以采用非绑定窗体。关键是先选定保存方式,再安排事件,而不是界面画完了才补几句保存代码。
Excel 的事件代码也有类似的时机问题。在 Worksheet_Change 里再次修改单元格,可能引发新的事件;如果暂时关闭 Application.EnableEvents,就要保证出错时也能恢复,不能让后续事件一直不执行。
两边都要理解事件,只是需要理解的业务时刻不同。
对照四:客户名称没有填,代码什么时候执行
下面这段 Excel 代码放到 客户 工作表的代码模块,不是标准模块。它检查 B 列修改后的内容,跳过标题行,发现空名称或错误值就提醒:
Private Sub Worksheet_Change(ByVal Target AsRange) Dim edited AsRange Dim cell AsRangeSet edited = Application.Intersect(Target, Me.Columns(2)) If edited Is Nothing Then Exit SubEnd IfForEach cell In edited.Cells If cell.Row >1Then If IsError(cell.Value2) Then MsgBox "客户名称不能是错误值。", vbExclamation Exit Sub ElseIf Len(Trim$(CStr(cell.Value2))) =0Then MsgBox "客户名称还没有填。", vbExclamation Exit SubEnd IfEnd If Next cellEnd Sub它是在单元格被改动之后提醒,不会撤销这次修改,也不是工作簿保存前的校验。这里只检查直接修改,公式重算不会触发 Worksheet_Change。
Access 新建一个绑定 客户 表的窗体,添加文本框,名称设为 txt客户名称,控件来源设为 客户名称。在窗体属性的「事件」页,把「更新前」设为「[事件过程]」,进入窗体模块放入下面的代码:
Private Sub Form_BeforeUpdate(Cancel AsInteger) If Len(Trim$(CStr(Nz(Me.txt客户名称.Value, "")))) =0Then Cancel =True MsgBox "请先填写客户名称,再保存记录。", vbExclamationEnd IfEnd Sub把客户名称清空,再尝试切换记录,窗体会在保存当前记录前执行检查,Cancel = True 阻止这次保存。用户仍可以补填名称,或者按 Esc 撤销编辑。这个检查只管通过该窗体保存的记录,不会替代数据表或其他写入入口的规则。
这两段不是完全等价的校验方案,特意放在一起,是为了看清事件时机:一个在单元格已经改变后提醒,一个在记录提交前允许取消。名称相似的事件,不能只换个过程名就当成同一回事。
空单元格和 Null,不能当成同一个东西
从 Excel 转到 Access,有些错误看起来很小,却会让判断完全变样。
Excel 里看起来空白的单元格,可能是真的没有内容,也可能有一个返回空字符串的公式。读到 VBA 里,还可能遇到日期、数字、文本和工作表错误值。
Access 记录中的 Null,表示没有已知的值。它不是数字 0,也不是长度为零的字符串。
判断 Null 应该使用 IsNull,不能指望 字段值 = Null 能像普通相等比较那样得到想要的结果。用 Nz 替换空值时,也应该明确替换成什么,以及这个替换是否符合业务含义。
例如,数量尚未录入,和数量已经确认是 0,并不是一回事。把两者都替换成 0,计算也许不再报错了,但「还没填」的信息也没了。
Nz 是 Access 提供的函数,不是 Excel VBA 默认自带的通用函数。复制一段带 Nz 的代码到 Excel,不能假定它会直接通过编译。
还有两个 .Text,也不要混用。
Excel 的 Range.Text 读到的是格式化后的显示文字,显示内容还可能受列宽等因素影响。处理原始值时,通常要考虑 Value 或 Value2,而不是把显示文字当成稳定的数据来源。
Access 文本框的 .Text 用来读取当前编辑文本,需要控件具有焦点;.Value 的含义和可用时机不同。不能因为 Excel 的单元格支持 .Text,就以为 Access 控件上的 .Text 也随时能读。
处理客户编号时,这些细节尤其重要。00123 是编号,不一定是整数 123。Excel 导入时要防止自动转换,Access 建字段时也应该明确采用文本类型。VBA 只是执行你的决定,不会替你识别业务含义。
性能问题,两边要先查的地方不同
Excel VBA 慢,经常慢在反复访问单元格。
逐格读取、逐格写入,再加上频繁选择工作表、触发重算和屏幕刷新,程序会把很多时间花在 Excel 对象交互上。对于一块连续区域,批量读入数组,在内存里处理,再批量写回,通常更值得考虑。
但关闭屏幕刷新、事件或自动计算,不是随手加在开头就结束的优化。应保存原来的状态,正常完成和出错退出时都恢复。尤其不要把计算模式恢复成你自己假定的值,影响用户其他工作簿。
Access 慢,则要先看取了多少数据、查询怎么写、关联字段有没有合适的索引,以及后端在哪里。
只需要某个客户最近的订单,却把所有客户的全部订单加载到窗体里,再逐条筛选;或者列表每显示一条记录,就额外做一次域查找,随着记录增多,都可能让等待变长。
索引也不是越多越好,它能帮助某些查找和连接,但会增加写入维护成本。链接到服务器数据库时,还要考虑查询能不能在服务器端完成,不能只凭前端代码少了几行就判断更快。
所以,不能一看到 Access 卡顿,就照搬 Excel 的 ScreenUpdating 思路。屏幕少重画一次,并不会让一个本来读了大量无关记录的查询自动变合理。
优化之前先区分:是在等界面,等逐条对象访问,还是等数据库返回结果。这个判断,比先套一段所谓的提速代码更重要。
出错之后,恢复现场的内容也不同
两边都使用 VBA 的错误处理机制。On Error GoTo、保存错误号和错误描述、释放对象,这些习惯可以直接沿用。
不过,一次操作做到一半时,留下的东西不同。
Excel 批量填表,写到中途出错,前面已经改过的单元格可能还在那里。不能指望运行宏之后,用户总能靠普通撤销把所有更改退回去。涉及原始数据时,我会先备份,或者先在新工作表生成结果,核对后再替换。
Access 里如果一个业务动作需要修改多条记录,甚至多张表,就要考虑它们是不是必须一起成功。
比如确认一张单据,既要保存主表,又要保存明细,还要写业务日志。如果其中一步失败,却留下其他几步的结果,系统可能出现用户很难自行修复的数据状态。
对于支持事务的数据源,可以把需要一起完成的数据库操作放入适当的事务中,成功就提交,失败就回滚。DAO、ADO 和不同后端的事务使用方式、覆盖范围并不完全相同,要按实际连接和数据源来安排。
事务也不能包办一切。数据库回滚了,不等于已经发出的邮件、生成的文件、调用过的外部接口都会自动撤回。绑定窗体自己的保存,也不能想当然地认为已被你另一个连接上的事务控制。
这也是为什么业务程序写到一定程度,必须把「数据库写入」和「外部动作」分清楚。只加一个错误提示框,解决不了执行一半的问题。

两边可以互相调用,但不要把职责混在一起
Excel VBA 可以连接数据库,使用 ADO 等方式读写数据;Access VBA 也可以创建 Excel 应用程序,写工作簿、套格式、生成图表。
所以,说「Excel VBA 不能操作数据库」不准确;说「Access VBA 只能在 Access 文件里干活」也不准确。
比较常见的配合,是 Access 负责客户、订单、明细这些结构化数据,Excel 负责给用户熟悉的分析和输出格式。不是非要让一个工具把另一个工具的工作全做完。
写跨程序代码时,对象归属需要更加明确。Access 里操作 Excel,最好明确拿到对应的 Excel 应用、工作簿和工作表对象,再访问区域,不要依赖当前哪个窗口恰好在前面。
ThisWorkbook 在 Excel 里指装着当前 VBA 代码的工作簿;ActiveWorkbook 指当前活动工作簿。两者可能不是同一个文件。Access 里也没有一个默认的 ThisWorkbook 可以供你直接使用。
提前绑定可以获得类型检查和编辑器提示,但需要管理对象库引用;后期绑定可以减少部分引用依赖,但不会自动消除 Office 版本、位数或运行环境差异。
最后还要分清,那个 Excel 实例是谁创建的。自己专门创建的实例,应该安排关闭和退出;连接到用户正在使用的 Excel,则不能清理时顺手把用户所有工作都关了。
这些问题不是语法难,是代码已经跨过了一个应用程序的范围,开始需要管理另一个程序的状态和生命周期。
从 Excel 转到 Access,应该补的是哪一部分
如果你已经会写 Excel VBA,我不建议重新从变量和循环学起。
字符串处理、文件操作、数组、字典、模块拆分、调试和错误处理,这些经验都可以继续用。先检查代码是否依赖 Excel 对象,把不依赖宿主的部分整理出来,很多函数不用重写。
真正需要补的,是关系型数据设计、主键和外键、SQL、DAO 或 ADO,以及 Access 窗体围绕记录执行的事件。
最好先做一个小但完整的练习:客户表加订单表,用客户 ID 关联;写一个查询筛出某个客户的订单;用绑定窗体查看和修改;再把结果输出到 Excel。
这个练习会让你实际碰到记录身份、查询条件、保存时机和跨程序调用。比把一个工作表的全部操作强行改写成记录集循环,更能帮助你理解 Access。
项目交付时,两边还要各自处理环境问题。Excel 宏通常放在 .xlsm 或 .xlam 中;Access 常用 .accdb 开发,也可以按需求生成 .accde,用合适的 Access Runtime 环境运行。
不管哪边,都要考虑宏安全设置、受信任位置、外部引用和 32/64 位兼容性。Access Runtime 不是一个可以随意设计和修改对象的完整开发环境;.accde 也不是数据库权限和数据保护的替代品。
多人使用 Access 时,还要安排每个人自己的前端副本和共享后端,或者连接服务器数据库。不能因为 VBA 代码在自己电脑上跑通,就认为多人直接打开同一个前端文件也一定没问题。
反过来,Access 开发者写 Excel 自动化,也需要补工作表对象、公式、区域批量读写和计算状态管理。会 SQL,不代表可以忽略 Excel 自己的计算和显示规则。
这张表说的是常见开发重点,不是能力的边界。两边都能循环,都能执行 SQL,也都能调用外部程序,只是宿主已经替你准备好的东西不同。
同样一段 VBA,语法正确,只说明语言这一关过了。它操作的对象是否存在,定位的是否是那条记录,错误时会不会留下半份数据,还需要按具体程序来判断。
会 Excel VBA,是学习 Access 的基础,不是障碍。把注意力从「单元格在哪儿」扩展到「数据之间是什么关系、什么时候应该保存」,很多原来需要自己拼起来的业务功能,就能借助 Access 已有的机制完成。
如果你正在把 Excel VBA 工具改成 Access 系统,或者不确定现有代码哪些能复用,欢迎公众号后台留言。