本节摘要:VBA 与 SQL 的结合点有四招:CurrentDb.Execute 跑动作语句、字符串拼接构造动态条件、OpenRecordset 直接打开一段 SQL、临时 QueryDef 承接复杂报表源。本节配一个完整案例——华彩的"业务员月度业绩一键结算",把四招串成一条工作流。
第 3 章你学会了写静态查询:建好、保存、反复跑。但真实系统里大量需求是动态的——月份由用户挑、业务员由用户选、连排序都可能现场变。这类"参数会呼吸"的任务,正解就是把 SQL 字符串交给 VBA 组装。本章收尾这讲,是前两节语法与对象课的会操。
最轻量的结合。凡 UPDATE、DELETE、INSERT、生成表这些不返回行的语句,一行搞定:
Dim db As Database Set db = CurrentDb db.Execute "UPDATE 订单 SET 状态 = '已发货' " & _ "WHERE ID = " & Me!订单ID, dbFailOnError
两个要点:dbFailOnError 参数务必带上——它让引擎级错误(比如违反完整性)就地抛异常,否则 Execute 会把失败吞得干干净净静悄悄;改数据之后的窗体显示要手动 Me.Requery 或 Form.Refresh 刷新,屏幕不会自动知道底层变了。
引号嵌套规则第一次正面登场:SQL 里文本值用单引号,VBA 字符串本身用双引号,遇到字段值往里拼时保持这个层次就基本不会乱。日期更讲究,Access 方言要求井号包裹,拼接时写作 "#" & Format(某日期, "yyyy-mm-dd") & "#"——不给格式化的结果有国际区歧义,这是个隐蔽坑。
用户在筛选面板上勾勾选选,程序按勾选结果现场缝制一句完整的 SELECT。看华彩促销分析屏的取数核心:
Dim 条件堆 As String, 最终Sql As String, rst As DAO.Recordset If Not IsNull(Me!起始日) Then 条件堆 = 条件堆 & " And 下单日期 >= #" & Format(Me!起始日, "yyyy-mm-dd") & "#" End If If Me!只看大单 = True Then 条件堆 = 条件堆 & " And 明细小计 >= 2000" End If 最终Sql = "SELECT * FROM 查询明细宽表 WHERE True" & 条件堆 Set rst = CurrentDb.OpenRecordset(最终Sql, dbOpenSnapshot)
这里藏着一个老手标志性的小技巧:WHERE True 开头。先放一个恒真条件,后面每项都以 And 前缀追加,省掉了"第一个条件不带 And、后续都带"的烦人分支判断。代码短三分之一定律在动态拼接场景体现得淋漓尽致。OpenRecordset 接受任意合法 SELECT 串直接当数据源,快照模式顺手保住性能。

第三招:取单个值的降维打击。 你只要一个数,却拿整张记录集来遍历?用 D 函数或者标量子查询一步到位:
' 今天该客户的累计金额 一个变量接住 Dim 今日合计 As Currency 今日合计 = Nz(DSum("明细小计", "查询明细宽表", _ "客户ID=" & Me!客户ID & " And 下单日期=Date()"), 0)
判据简单:要的是"一个格子"就用 D 函数家族;要的是"一批行"才动用 OpenRecordset。用错了也不报错,只是慢和丑。
第四招:临时 QueryDef 养一次性视图。 报表需要一个结构复杂的动态数据源,而报表控件又只认具名对象。方案:运行时创建临时查询定义塞进动态 SQL,报表指向它:
Dim qdf As DAO.QueryDef ' 目标查询已存在于库中 这里只替换其 SQL 正文 Set qdf = CurrentDb.QueryDefs("查询_结算底稿") qdf.SQL = 最终Sql ' 热更新定义 DoCmd.OpenReport "提成结算单", acViewPreview
这个模式叫"可重编程视图",比生成临时真表优雅得多:不留垃圾数据、事务安全、并发友好。提成结算案例的最外层正是这么组织的:筛选屏收集参数 → 动态 SQL 生成底稿 → 临时定义热更新 → 报表出纸 → 最后一步 用 Execute 把每人提成额回写到业绩表存档。全流程一个按钮完成,周姐笑开了花。
动态 SQL 的排错口诀上一图给过了,纪律再补三条:
Replace(输入,"'","''")),这是本地版的注入防线,成本一行动作,收益是单据备注里出现英文撇号时系统不会瘫;组合拳练完最怕每事都用第四招。判断顺序建议固定如下:
| 需求形态 | 首选武器 | 反例警示 |
|---|---|---|
| 条件永远固定的常用提问 | 保存好的查询对象 | 别在代码里重写现成 SQL |
| 只要一个数 | D 函数家族 | 别开记录集遍历取一格 |
| 要一批行逐条处理 | OpenRecordset | 别让 MsgBox 循环一万次 |
| 参数随屏幕变化的动态源 | QueryDef 热更新 | 别给每种组合存一个真表 |
还有一个根治拼接之痛的高级姿势值得提前知道:把动态条件写成带参数的正式查询,运行时由 VBA 给它的 Parameters 集合赋值,而不是往字符串里塞变量。日期、文本从此不必操心引号转义,引擎还顺手帮你做了类型校验。拼接版好写但脆,参数版多两行 setup 但稳——重要的对外流程,我们一律落到参数版。
宏讲边界、语言讲积木、DAO 讲对象、SQL 讲弹药——至此 Access 编程的四大件集齐。检验标准也很朴素:你现在应当能独立读懂本书源案例里任何一个模块化按钮背后的完整逻辑链,并模仿着为你的业务写出第一个自动化零件。练手题材不必宏大:把第 4 章某个只跑宏的按钮升级成带错误日志的 VBA 版本,对比两版差异,你就能真切感到这一章四节课各自贡献了什么。
下一章换一门视野:Access 不是孤岛,进出数据的口岸怎么管理。