Access 用 VBA 操作 SQLite,不用装任何驱动

Hi,大家好!

上一篇文章聊了 Access 和 SQLite 的定位差异------一个是桌面应用开发平台,一个是嵌入式数据库引擎,本来不在一个赛道上比。

文章发出去之后,后台收到不少留言,问的是同一件事:

"那在 Access 里到底怎么操作 SQLite?"

好问题。既然两个工具可以配合着用,那总得有个门路把它们连起来。

说到连接方案,网上搜一圈,几乎清一色都在讲 ODBC------装一个 SQLite3 ODBC Driver,然后 ADO 连上去。这条路确实能走。

但它有个小尴尬:SQLite 官方根本没有提供 ODBC 驱动。

有没有更干净的办法?

有。直接调 SQLite 自带的 sqlite3.dll。一个不到 2MB 的 dll 文件,跟你的 Access 文件放同一个文件夹就行------零安装、零配置、零依赖。 拷到 U 盘里带走都行。

这才是真正的"拿来即用"。今天就把这套方案从头到尾讲清楚。

第一步:拿到 sqlite3.dll

去 SQLite 官方下载页:

https://sqlite.org/download.html

找到 Precompiled Binaries for Windows 区域,下载 sqlite-dll-win-x86-xxxxxxx.zip(32 位)或 sqlite-dll-win-x64-xxxxxxx.zip(64 位),注意,如果你的CPU是arm架构的,要下载arm版。

注意位数跟 Office 走,不是跟 Windows 走。 Office 32 位就下 32 位 dll,Office 64 位就下 64 位 dll。怎么看 Office 位数?Access 里按 Ctrl+G 打开立即窗口,输入:

复制代码
? IIf(VBA7, "64位", "32位")

解压出 sqlite3.dll,放到 Access 文件同级目录。完事儿,不用任何安装。

第二步:建一个标准模块,声明 API

新建模块 modSQLite,把下面代码贴进去。所有 SQLite 操作都靠这几行 API 声明:

vb 复制代码
Option Compare Database
Option Explicit

'====================================================
' SQLite3 C API 声明(UTF-16 版本)
'====================================================

'打开/创建数据库(UTF-16 文件名)
Public Declare PtrSafe Function api_sqlite3_open16 Lib "sqlite3" _
    Alias "sqlite3_open16" ( _
    ByVal filename As LongPtr, _
    ByRef ppDb As LongPtr _
) As Long

'关闭数据库
Public Declare PtrSafe Function api_sqlite3_close Lib "sqlite3" _
    Alias "sqlite3_close" ( _
    ByVal db As LongPtr _
) As Long

'执行非查询 SQL(需要 UTF-8)
Public Declare PtrSafe Function api_sqlite3_exec Lib "sqlite3" _
    Alias "sqlite3_exec" ( _
    ByVal db As LongPtr, _
    ByVal sql As LongPtr, _
    ByVal callback As LongPtr, _
    ByVal arg As LongPtr, _
    ByRef errMsg As LongPtr _
) As Long

'预编译 SQL 语句(UTF-16 版本)
Public Declare PtrSafe Function api_sqlite3_prepare16_v2 Lib "sqlite3" _
    Alias "sqlite3_prepare16_v2" ( _
    ByVal db As LongPtr, _
    ByVal zSql As LongPtr, _
    ByVal nByte As Long, _
    ByRef ppStmt As LongPtr, _
    ByRef pzTail As LongPtr _
) As Long

'执行预编译语句的下一步
Public Declare PtrSafe Function api_sqlite3_step Lib "sqlite3" _
    Alias "sqlite3_step" ( _
    ByVal stmt As LongPtr _
) As Long

'获取列数
Public Declare PtrSafe Function api_sqlite3_column_count Lib "sqlite3" _
    Alias "sqlite3_column_count" ( _
    ByVal stmt As LongPtr _
) As Long

'获取列名(UTF-16)
Public Declare PtrSafe Function api_sqlite3_column_name16 Lib "sqlite3" _
    Alias "sqlite3_column_name16" ( _
    ByVal stmt As LongPtr, _
    ByVal N As Long _
) As LongPtr

'获取文本列值(返回 UTF-16 指针)
Public Declare PtrSafe Function api_sqlite3_column_text16 Lib "sqlite3" _
    Alias "sqlite3_column_text16" ( _
    ByVal stmt As LongPtr, _
    ByVal iCol As Long _
) As LongPtr

'获取整数列值
Public Declare PtrSafe Function api_sqlite3_column_int64 Lib "sqlite3" _
    Alias "sqlite3_column_int64" ( _
    ByVal stmt As LongPtr, _
    ByVal iCol As Long _
) As LongLong

'获取浮点列值
Public Declare PtrSafe Function api_sqlite3_column_double Lib "sqlite3" _
    Alias "sqlite3_column_double" ( _
    ByVal stmt As LongPtr, _
    ByVal iCol As Long _
) As Double

'获取列类型
Public Declare PtrSafe Function api_sqlite3_column_type Lib "sqlite3" _
    Alias "sqlite3_column_type" ( _
    ByVal stmt As LongPtr, _
    ByVal iCol As Long _
) As Long

'销毁预编译语句
Public Declare PtrSafe Function api_sqlite3_finalize Lib "sqlite3" _
    Alias "sqlite3_finalize" ( _
    ByVal stmt As LongPtr _
) As Long

'获取错误信息(UTF-16)
Public Declare PtrSafe Function api_sqlite3_errmsg16 Lib "sqlite3" _
    Alias "sqlite3_errmsg16" ( _
    ByVal db As LongPtr _
) As LongPtr

'释放内存
Public Declare PtrSafe Sub api_sqlite3_free Lib "sqlite3" _
    Alias "sqlite3_free" ( _
    ByVal ptr As LongPtr _
)

'辅助:获取 UTF-16 字符串长度
Public Declare PtrSafe Function api_lstrlenW Lib "kernel32" _
    Alias "lstrlenW" ( _
    ByVal lpString As LongPtr _
) As Long

'辅助:内存拷贝
Public Declare PtrSafe Sub api_CopyMemory Lib "kernel32" _
    Alias "RtlMoveMemory" ( _
    ByRef Destination As Any, _
    ByVal Source As LongPtr, _
    ByVal Length As Long _
)

'辅助:添加 DLL 搜索路径
Public Declare PtrSafe Function api_SetDllDirectoryW Lib "kernel32" _
    Alias "SetDllDirectoryW" ( _
    ByVal lpPathName As LongPtr _
) As Long

'====================================================
' 辅助函数
'====================================================

'--- 初始化:告诉 Windows 去哪找 sqlite3.dll ---
'每个用到 sqlite3 的过程,第一行先调这个
Public Sub InitSQLite(Optional ByVal dllFolder As String)
    If Len(dllFolder) = 0 Then
        dllFolder = CurrentProject.Path
    End If
    api_SetDllDirectoryW StrPtr(dllFolder)
End Sub

'将 UTF-16 字符串指针转换为 VBA String
Public Function PtrToStr(ByVal ptr As LongPtr) As String
    Dim length As Long
    If ptr = 0 Then Exit Function
    length = api_lstrlenW(ptr)
    If length = 0 Then Exit Function
    PtrToStr = String$(length, vbNullChar)
    api_CopyMemory ByVal StrPtr(PtrToStr), ptr, length * 2
End Function

'将 VBA String 转为 UTF-8 字节数组(用于 sqlite3_exec)
Public Function ToUtf8(ByVal str As String) As Byte()
    ToUtf8 = StrConv(str & vbNullChar, vbFromUnicode)
End Function

'获取错误描述
Public Function GetErrMsg(ByVal db As LongPtr) As String
    GetErrMsg = PtrToStr(api_sqlite3_errmsg16(db))
End Function

这里有一个容易踩的坑,单独说一下。

上面声明了 api_sqlite3_open16 Lib "sqlite3",代码里只写了 "sqlite3",没有写全路径------Windows 会按自己的规则去找这个 dll。这套规则里包括系统目录(System32)、PATH 环境变量、当前工作目录......但不包括你的 accdb 文件所在目录

所以把 sqlite3.dll 放在 accdb 旁边是不够的,系统根本不会来这找。

解决办法就是加了 InitSQLite 这个函数:它调用 SetDllDirectoryW,把 CurrentProject.Path(也就是你的 accdb 所在目录)临时加入 DLL 搜索路径。这样不管 accdb 在哪,sqlite3.dll 放在同目录就能被找到。

以后每次写操作 SQLite 的过程,第一行先写 InitSQLite,养成习惯就行。

第三步:创建数据库

核心就一行 api_sqlite3_open16------文件不存在会自动创建,Access 的 CurrentDb 还得先有个 accdb 文件呢,SQLite 省了这一步。

vb 复制代码
'--- 创建数据库 ---
Public Function CreateDB(ByVal dbPath As String) As LongPtr
    Dim db As LongPtr
    Dim ret As Long
    
    InitSQLite  '告诉系统 sqlite3.dll 在哪
    
    '用 UTF-16 版 API,直接传 VBA 字符串指针
    '返回值 0 = SQLITE_OK
    ret = api_sqlite3_open16(StrPtr(dbPath), db)
    
    If ret <> 0 Then
        Debug.Print "打开数据库失败:" & GetErrMsg(db)
        api_sqlite3_close db
        Exit Function
    End If
    
    CreateDB = db
End Function

'--- 打开已有数据库 ---
Public Function OpenDB(ByVal dbPath As String) As LongPtr
    '本质还是 sqlite3_open16(打开或创建),但语义上用于已有数据库
    OpenDB = CreateDB(dbPath)
End Function

第四步:建表

拿到数据库句柄之后,用 sqlite3_exec 执行 DDL 语句。这个 API 只接收 UTF-8,所以加一个转换包装:

vb 复制代码
'--- 执行非查询 SQL(建表、插入、更新、删除) ---
Public Sub ExecSQL(ByVal db As LongPtr, ByVal sql As String)
    Dim utf8() As Byte
    Dim ret As Long
    
    utf8 = ToUtf8(sql)  '转为 UTF-8
    
    ret = api_sqlite3_exec(db, VarPtr(utf8(0)), 0, 0, 0)
    
    If ret <> 0 Then
        Err.Raise vbObjectError + 1, "modSQLite.ExecSQL", GetErrMsg(db)
    End If
End Sub

建表试一下:

vb 复制代码
Sub Demo_CreateTable()
    Dim db As LongPtr
    
    InitSQLite  '告诉系统去哪找 sqlite3.dll
    db = CreateDB(CurrentProject.Path & "\test.db")
    
    ExecSQL db, "CREATE TABLE IF NOT EXISTS t_employee (" & _
                 "  id       INTEGER PRIMARY KEY AUTOINCREMENT," & _
                 "  name     TEXT    NOT NULL," & _
                 "  dept     TEXT," & _
                 "  position     TEXT," & _
                 "  hire_date TEXT," & _
                 "  salary     REAL" & _
                 ");"
    
    Debug.Print "数据库和表创建完成"
    api_sqlite3_close db
End Sub

这里我用DataGrip给大家看一下表是否有创建成功,

目前表中无数据

第五步:插入数据

建表和插入用的是同一个 ExecSQL,底层都是 sqlite3_exec

vb 复制代码
Sub Demo_InsertData()
    Dim db As LongPtr
    
    InitSQLite
    db = OpenDB(CurrentProject.Path & "\test.db")  '数据库已存在,直接打开
    
    '单条插入
    ExecSQL db, "INSERT INTO t_employee (name, dept, position, hire_date, salary) " & _
                 "VALUES ('Zhang San', 'Tech', 'Sr. Developer', '2025-06-01', 15000);"
    Debug.Print " 插入 1 条"
    
    '批量插入(建议包事务,不然每一条 INSERT 都是独立事务,慢)
    ExecSQL db, "BEGIN TRANSACTION;"
    ExecSQL db, "INSERT INTO t_employee (name, dept, position, hire_date, salary) " & _
                 "VALUES ('Li Si', 'Finance', 'Accountant', '2024-03-15', 12000);"
    ExecSQL db, "INSERT INTO t_employee (name, dept, position, hire_date, salary) " & _
                 "VALUES ('Wang Wu', 'Marketing', 'Manager', '2023-09-01', 18000);"
    ExecSQL db, "INSERT INTO t_employee (name, dept, position, hire_date, salary) " & _
                 "VALUES ('Zhao Liu', 'HR', 'Specialist', '2025-01-10', 9000);"
    ExecSQL db, "COMMIT;"
    Debug.Print "批量插入 3 条"
    
    api_sqlite3_close db
End Sub

第六步:查询数据

查询不能走 sqlite3_exec(它不支持取结果集),要用 sqlite3_prepare16_v2 + sqlite3_step + sqlite3_column_* 这套流程:

vb 复制代码
'--- 查询并输出到立即窗口 ---
Public Sub QueryToDebug(ByVal db As LongPtr, ByVal sql As String)
    Dim stmt As LongPtr, ret As Long
    Dim cols As Long, i As Long
    
    '1. 预编译 SQL
    ret = api_sqlite3_prepare16_v2(db, StrPtr(sql), -1, stmt, 0)
    If ret <> 0 Then
        Debug.Print " 预编译失败:" & GetErrMsg(db)
        Exit Sub
    End If
    
    cols = api_sqlite3_column_count(stmt)
    
    '2. 打印列名
    For i = 0 To cols - 1
        Debug.Print PtrToStr(api_sqlite3_column_name16(stmt, i)) & vbTab;
    Next
    Debug.Print
    Debug.Print String(60, "-")
    
    '3. 逐行读取
    Do
        ret = api_sqlite3_step(stmt)
        If ret = 100 Then  'SQLITE_ROW = 100
            For i = 0 To cols - 1
                Select Case api_sqlite3_column_type(stmt, i)
                    Case 1: Debug.Print api_sqlite3_column_int64(stmt, i);     'INTEGER
                    Case 2: Debug.Print api_sqlite3_column_double(stmt, i);    'FLOAT
                    Case 3: Debug.Print PtrToStr(api_sqlite3_column_text16(stmt, i));  'TEXT
                    Case 5: Debug.Print "NULL";
                    Case Else: Debug.Print PtrToStr(api_sqlite3_column_text16(stmt, i));
                End Select
                If i < cols - 1 Then Debug.Print vbTab;
            Next
            Debug.Print
        Else
            Exit Do
        End If
    Loop
    
    '4. 释放
    api_sqlite3_finalize stmt
End Sub

试试效果:

vb 复制代码
Sub Demo_Query()
    Dim db As LongPtr
    
    InitSQLite
    db = OpenDB(CurrentProject.Path & "\test.db")  '直接打开已有数据库
    QueryToDebug db, "SELECT * FROM t_employee ORDER BY salary DESC;"
    api_sqlite3_close db
End Sub

立即窗口输出:

复制代码
id      name      dept        position        hire_date     salary
-------------------------------------------------------------------
3       Wang Wu   Marketing   Manager         2023-09-01    18000
1       Zhang San Tech        Sr. Developer   2025-06-01    15000
2       Li Si     Finance     Accountant      2024-03-15    12000
4       Zhao Liu  HR          Specialist      2025-01-10    9000

我们再到DataGrip查一下结果,验证一下结果,结果是正确的

常见问题

"找不到 sqlite3.dll"

把 dll 放到下面任一位置就行:

  • Access 文件同目录(推荐,拷给谁都能用)
  • C:\Windows\System32(64 位 Office + 64 位 dll)
  • C:\Windows\SysWOW64(32 位 Office + 32 位 dll)

"报错:找不到入口点"

dll 位数跟 Office 位数不匹配。回到第一步重新确认。

"插入数据慢"

默认每条 INSERT 都是独立事务。解决方式上面写了------用 BEGIN TRANSACTION / COMMIT 包起来。100 条数据,不包事务可能要几秒,包了事务不到 0.1 秒。

"中文乱码"

这套方案用的是 UTF-16 API(sqlite3_open16sqlite3_prepare16_v2sqlite3_column_text16),跟 VBA 原生编码一致,理论上不会乱码。只在 sqlite3_exec 时做了 UTF-8 转换,那里注意 StrConv(sql & vbNullChar, vbFromUnicode) 的写法就行。

总结

方案 部署 查询难度 推荐指数
第三方 ODBC 需安装驱动 简单 ⭐⭐⭐
直接调 sqlite3.dll 带一个 dll 中等 ⭐⭐⭐⭐⭐
调 sqlite3.exe 带一个 exe 麻烦 ⭐⭐

直接调 dll 是分发性最好、最干净的方式。 第三方 ODBC 驱动需要管理员权限安装,sqlite3.exe 每次启进程又慢又丑。一个不到 2MB 的 sqlite3.dll,跟着 accdb 走,没有任何额外的安装步骤------这才是 Access + SQLite 搭配的正确打开方式。

相关推荐
Niyy_1 小时前
WASM 的使用笔记
jvm·笔记·wasm
小L写Java1 小时前
第六章:JVM 调优实战 —— GC日志分析、内存溢出排查与线上问题定位
java·jvm
字节跳动数据库1 小时前
火山引擎 Milvus 发布官方 CLI + Skill ,终端与对话双通道掌控向量数据库
数据库·人工智能
夜白宋1 小时前
【Redis深入】一、快的原因
数据库·redis·缓存
念越1 小时前
【数据库系统概论期末复习】 绪论重点与常考题重点与常考题整理第一章
数据库·数据库系统概论
SXJR1 小时前
langchain4j是如何保证tools或者funcation call不出错的
java·网络·数据库·ai·语言模型
谷谷地图下载器1 小时前
全球、台湾省的无水印·街景数据(离线数据),专为可视化项目定制,支持国产化
javascript·c++·3d·arcgis·sqlite
AIMath~2 小时前
兼容pymongo=4.16版本如何安装mongodb
数据库·mongodb
念恒123062 小时前
MySQL连接池原理与简易网站数据流动是如何进行的
数据库·mysql