夜雨聆风学习资料网

ARTICLE · 1139364

同样是 VBA,为什么 Excel 的代码搬到 Access 就不能用?

同样是 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(数字,长整型)、金额(货币)。

两边都录入这两位客户,其他列暂时可以不填:

客户ID
客户名称
地区
业务员
101
甲公司
上海
李明
102
乙公司
北京
王芳

订单表录入订单 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 IfEndFunction

Access 不需要先知道记录显示在哪一行,按主键查询即可。参数单独传入,不拼客户名称或其他用户文字:

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.CloseEndFunction

Excel 的立即窗口执行 ? 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 rowIndexEndFunction

Access 把条件和新值交给参数查询,一次执行更新,返回受影响的记录数:

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)))EndFunction

Access 可以使用域聚合函数 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 自己的计算和显示规则。

比较内容
Excel VBA 常见重点
Access VBA 常见重点
操作对象
工作簿、工作表、区域
记录集、查询、窗体、报表
定位数据
区域位置,也可按业务键查找
主键、查询条件、关系
批量处理
数组、区域批量读写
SQL、记录集、适当的事务
执行时机
宏、工作簿和工作表事件
控件、记录和窗体事件
计算分工
公式、透视表与 VBA 配合
查询、表达式与 VBA 配合

这张表说的是常见开发重点,不是能力的边界。两边都能循环,都能执行 SQL,也都能调用外部程序,只是宿主已经替你准备好的东西不同。

同样一段 VBA,语法正确,只说明语言这一关过了。它操作的对象是否存在,定位的是否是那条记录,错误时会不会留下半份数据,还需要按具体程序来判断。

会 Excel VBA,是学习 Access 的基础,不是障碍。把注意力从「单元格在哪儿」扩展到「数据之间是什么关系、什么时候应该保存」,很多原来需要自己拼起来的业务功能,就能借助 Access 已有的机制完成。


如果你正在把 Excel VBA 工具改成 Access 系统,或者不确定现有代码哪些能复用,欢迎公众号后台留言。

点击这里阅读原文

相关学习资料