Access 复杂业务计算,SQL 写在 VBA 还是存储过程?

摘要: 成本核算、库龄分析、订单分摊这些复杂业务计算,该写在 VBA 里拼 SQL,还是写成存储过程?本文从实战角度对比两种方案的适用场景、维护成本和性能差异。

Hi,大家好!

前两天有人问我:"我们有个成本核算逻辑,要循环查表、多表关联、临时汇总,现在写在 VBA 里,每次算一遍要 2 分钟。有人建议我改成存储过程,但我不确定值不值得。"

这个问题很典型。Access 项目做大了,复杂计算总会遇到:到底该写在 VBA 里,还是扔到存储过程?

今天就来聊聊这个选择背后的边界。

先说结论:后端决定技术栈

如果你的 Access 后端是:

后端类型 推荐方案 原因
Access 数据库(.accdb) VBA 拼 SQL Access 不支持存储过程
SQL Server 存储过程 复杂逻辑性能好,易维护
MySQL 存储过程 同 SQL Server
PostgreSQL 存储过程 + 函数 PostgreSQL 函数更灵活

很多人以为"Access 也能写存储过程",其实Access 数据库本身不支持存储过程。你能写的只有:

  • 保存的查询(可以带参数)
  • VBA 函数

所以这个问题真正的边界是:后端是 Access 还是服务器数据库

场景 1: Access 后端 --- VBA 是唯一选择

如果后端是 .accdb.mdb,你只能在 VBA 里写业务逻辑。

示例:订单金额分摊

业务需求:一张订单有多个明细,每个明细按比例分摊订单级的运费和折扣。

方案 1: 单条 UPDATE(不推荐)

vba 复制代码
Sub 分摊订单费用_慢()
    Dim rs As DAO.Recordset
    Dim 订单总金额 As Currency, 运费 As Currency
    
    ' 先查订单总金额
    订单总金额 = DLookup("SUM(金额)", "订单明细", "订单号='SO2024001'")
    运费 = DLookup("运费", "订单表", "订单号='SO2024001'")
    
    ' 逐条更新明细
    Set rs = CurrentDb.OpenRecordset("SELECT * FROM 订单明细 WHERE 订单号='SO2024001'")
    Do Until rs.EOF
        rs.Edit
        rs!分摊运费 = 运费 * (rs!金额 / 订单总金额)
        rs.Update
        rs.MoveNext
    Loop
    rs.Close
End Sub

问题:

  • 每条明细都要 Edit + Update,100 条明细就是 100 次写操作
  • DLookup 每次都查数据库,慢

方案 2: 批量 UPDATE(推荐)

vba 复制代码
Sub 分摊订单费用_快()
    Dim sql As String
    
    ' 一条 SQL 搞定
    sql = "UPDATE 订单明细 INNER JOIN " & _
          "(SELECT 订单号, SUM(金额) AS 总金额, First(运费) AS 运费 " & _
          " FROM 订单明细 INNER JOIN 订单表 USING(订单号) " & _
          " WHERE 订单号='SO2024001' GROUP BY 订单号) AS T " & _
          "ON 订单明细.订单号 = T.订单号 " & _
          "SET 订单明细.分摊运费 = T.运费 * (订单明细.金额 / T.总金额)"
    
    CurrentDb.Execute sql, dbFailOnError
End Sub

优势:

  • 一次批量更新,性能提升 10-100 倍
  • 不需要循环,代码更简洁

什么时候 VBA 拼 SQL 不够用?

  1. 逻辑太复杂,SQL 写不出来

    • 例:库存先进先出(FIFO)计算,需要按日期排序逐笔扣减
    • 例:树形 BOM 展开,递归层级不确定
  2. 需要大量临时表

    • Access SQL 不支持 WITH 递归、不支持临时表
    • 只能在 VBA 里创建真实表,用完再删
  3. 性能瓶颈

    • 100 万条数据做复杂汇总,VBA 循环根本跑不动

这时候就该考虑:要么优化算法,要么迁移到 SQL Server。

场景 2: SQL Server 后端 --- 存储过程的正确用法

如果后端已经是 SQL Server,复杂业务逻辑就该写成存储过程。

示例:库存先进先出(FIFO)计算

业务需求:出库时按入库日期顺序扣减库存,算出加权平均成本。

存储过程:

sql 复制代码
CREATE PROCEDURE sp_计算FIFO成本
    @出库单号 NVARCHAR(50),
    @产品编号 NVARCHAR(50),
    @出库数量 DECIMAL(18,2)
AS
BEGIN
    DECLARE @剩余数量 DECIMAL(18,2) = @出库数量
    DECLARE @总成本 DECIMAL(18,4) = 0
    
    -- 按入库日期排序
    DECLARE cur CURSOR FOR
        SELECT 批次号, 库存数量, 单价
        FROM 库存明细
        WHERE 产品编号 = @产品编号 AND 库存数量 > 0
        ORDER BY 入库日期
    
    DECLARE @批次号 NVARCHAR(50), @批次数量 DECIMAL(18,2), @单价 DECIMAL(18,4)
    
    OPEN cur
    FETCH NEXT FROM cur INTO @批次号, @批次数量, @单价
    
    WHILE @@FETCH_STATUS = 0 AND @剩余数量 > 0
    BEGIN
        DECLARE @本次扣减 DECIMAL(18,2)
        
        IF @批次数量 >= @剩余数量
        BEGIN
            SET @本次扣减 = @剩余数量
            SET @剩余数量 = 0
        END
        ELSE
        BEGIN
            SET @本次扣减 = @批次数量
            SET @剩余数量 = @剩余数量 - @批次数量
        END
        
        -- 累计成本
        SET @总成本 = @总成本 + (@本次扣减 * @单价)
        
        -- 扣减库存
        UPDATE 库存明细
        SET 库存数量 = 库存数量 - @本次扣减
        WHERE 批次号 = @批次号
        
        FETCH NEXT FROM cur INTO @批次号, @批次数量, @单价
    END
    
    CLOSE cur
    DEALLOCATE cur
    
    -- 返回加权平均成本
    SELECT @总成本 / @出库数量 AS 加权成本
END

VBA 调用:

vba 复制代码
Sub 计算出库成本()
    Dim conn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rs As ADODB.Recordset
    
    Set conn = New ADODB.Connection
    conn.Open "Provider=SQLOLEDB;Data Source=服务器;Initial Catalog=仓库管理;Integrated Security=SSPI;"
    
    Set cmd = New ADODB.Command
    cmd.ActiveConnection = conn
    cmd.CommandText = "sp_计算FIFO成本"
    cmd.CommandType = adCmdStoredProc
    
    cmd.Parameters.Append cmd.CreateParameter("@出库单号", adVarChar, adParamInput, 50, "OUT2024001")
    cmd.Parameters.Append cmd.CreateParameter("@产品编号", adVarChar, adParamInput, 50, "P001")
    cmd.Parameters.Append cmd.CreateParameter("@出库数量", adDecimal, adParamInput, , 100)
    
    Set rs = cmd.Execute
    
    Debug.Print "加权成本:", rs!加权成本
    
    rs.Close
    conn.Close
End Sub

为什么用存储过程?

  1. 性能好:

    • 游标在服务器端执行,不需要往返传输数据
    • SQL Server 会缓存执行计划
  2. 事务安全:

    • 整个 FIFO 计算在一个事务里,要么全成功,要么全回滚
    • VBA 里很难控制这么复杂的事务
  3. 易维护:

    • 业务逻辑集中在数据库端
    • 不用每次改逻辑都发布新版 Access 前端

什么时候不该用存储过程?

1. 简单查询

vba 复制代码
' 不要为了一个简单查询写存储过程
Dim rs As ADODB.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT * FROM 客户表 WHERE 地区='华东'")

存储过程写起来反而麻烦,维护成本也高。

2. 需要频繁改动的逻辑

如果业务规则一天一变,写在存储过程里每次都要:

  1. 连上 SQL Server
  2. 修改存储过程
  3. 通知用户重启 Access

不如写在 VBA 里,改完发个前端更新包就行。

3. 涉及大量 UI 交互

vba 复制代码
' 这种逻辑不适合存储过程
For Each 产品 In 产品列表
    If MsgBox("是否计算 " & 产品.名称 & " 的成本?", vbYesNo) = vbYes Then
        ' 调用计算逻辑
    End If
Next

存储过程不能弹 MsgBox,这种逻辑必须在 VBA 里。

性能对比:真实案例

我之前做过一个项目,有个成本核算功能:

方案 执行时间 说明
VBA 循环 + Recordset 120 秒 10 万条明细,逐条读取计算
VBA 拼 SQL(批量 UPDATE) 15 秒 改成 JOIN + 批量更新
SQL Server 存储过程 2 秒 迁移后端 + 存储过程

结论:

  • VBA 循环是最慢的,能不用就不用
  • VBA 拼 SQL 性能可以接受,10 万条以内没问题
  • 存储过程是性能最优解,但要后端支持

我的选择原则

Access 后端(单机或小团队)

  • 简单 CRUD: 直接用绑定窗体,不写代码
  • 单表统计: 保存的查询 + VBA 调用
  • 复杂计算: VBA 拼 SQL,能批量就不循环
  • 实在写不出 SQL: VBA 循环 + 临时表

SQL Server 后端(中大型项目)

  • 查询类: VBA 拼 SQL(SELECT)
  • 复杂写入: 存储过程(INSERT/UPDATE/DELETE)
  • 定时任务: SQL Agent + 存储过程
  • 报表统计: 存储过程 + 物化视图

迁移建议

如果你的 Access 项目遇到性能瓶颈,不要急着全部重写。可以这样分步迁移:

  1. 第一步:优化 VBA SQL

    • 把循环改成批量操作
    • 减少 DLookup 调用
    • 用临时表代替多次 JOIN
  2. 第二步:迁移后端到 SQL Server

    • 数据表迁移到 SQL Server
    • Access 前端改用链接表
    • VBA 代码先不动
  3. 第三步:逐步改存储过程

    • 先改最慢的那几个功能
    • 对比性能是否真的提升
    • 再决定是否继续改其他功能

不要一上来就全改,风险太大。

常见误区

误区 1:"存储过程一定比 VBA 快"

不一定。如果你的 VBA 代码本身写得好(批量操作、减少网络传输),性能不会差太多。

只有在复杂循环、临时表、递归这些场景,存储过程才有明显优势。

误区 2:"Access 可以写存储过程"

Access 数据库本身不支持存储过程

你可以写成:

  • VBA 公共函数(在模块里)
  • 保存的查询(可以带参数)

但这些不是真正的存储过程,性能和事务控制都差很多。

误区 3:"所有逻辑都该写在数据库端"

不是的。

UI 交互、权限判断、日志记录这些逻辑更适合写在 VBA 里,因为:

  • 要弹窗、要读当前用户、要写本地日志
  • 服务器端做不了这些事

技术选型的边界

最后总结一下选择边界:

写在 VBA 里:

  • Access 后端(没得选)
  • 简单查询和写入
  • UI 交互相关逻辑
  • 需要频繁改动的规则

写成存储过程:

  • SQL Server / MySQL / PostgreSQL 后端
  • 复杂的多表计算
  • 需要事务保证的批量操作
  • 定时任务和后台作业

两者结合:

  • VBA 负责 UI、权限、本地逻辑
  • 存储过程负责核心业务计算
  • 通过 ADO 参数化调用存储过程

不要追求"哪种方案最好",而是看当前项目处于什么阶段、后端是什么、团队能力如何

选对了工具,开发快、维护稳、性能好。选错了工具,改一次需求就是一次大手术。

相关推荐
宝桥南山2 天前
Microsoft Fabric - 简单尝试一下Microsoft Fabric .NET SDK
microsoft·微软·.net·.netcore·powerbi·fabric
hqyjzsb2 天前
规划工商管理大学成长:搭建四层能力体系,重视高阶的 AI 能力建设
开发语言·人工智能·python·microsoft·职场和发展·数据挖掘·业界资讯
ai_finder3 天前
买卖点预警系统是怎么工作的?从自然语言到盯盘任务的一次工程拆解
人工智能·科技·microsoft·金融
m0_466525294 天前
从火柴人看护画面看云从科技生态企业的隐私保护实践
大数据·人工智能·科技·microsoft
大模型码小白4 天前
告别造假数据,直接连数据库查真实时序数据喂给 TimechoAI 大模型
java·数据库·人工智能·microsoft·架构
海盗12344 天前
微软技术日报 2026-09-14:Rust 升为微软一级语言,云业务重组为智能体与基础设施
开发语言·microsoft·rust
ManageEngineITSM5 天前
什么是IT服务连续性管理?灾难恢复与业务连续性一文讲清
数据库·microsoft·工单系统·变更管理
秦哈哈5 天前
【Hello Agents】学习笔记(二)
笔记·学习·microsoft
承渊政道5 天前
Python IDLE鸿蒙PC适配全记录:用 ArkUI 重建编辑、运行、Shell 与基础调试闭环
python·microsoft·harmonyos·鸿蒙系统·pc端