Hi,大家好!
上一篇文章聊了 Access 和 SQLite 的定位差异------一个是桌面应用开发平台,一个是嵌入式数据库引擎,本来不在一个赛道上比。
文章发出去之后,后台收到不少留言,问的是同一件事:
"那在 Access 里到底怎么操作 SQLite?"
好问题。既然两个工具可以配合着用,那总得有个门路把它们连起来。
说到连接方案,网上搜一圈,几乎清一色都在讲 ODBC------装一个 SQLite3 ODBC Driver,然后 ADO 连上去。这条路确实能走。
但它有个小尴尬:SQLite 官方根本没有提供 ODBC 驱动。
有没有更干净的办法?
有。直接调 SQLite 自带的 sqlite3.dll。一个不到 2MB 的 dll 文件,跟你的 Access 文件放同一个文件夹就行------零安装、零配置、零依赖。 拷到 U 盘里带走都行。
这才是真正的"拿来即用"。今天就把这套方案从头到尾讲清楚。
第一步:拿到 sqlite3.dll
去 SQLite 官方下载页:
找到 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_open16、sqlite3_prepare16_v2、sqlite3_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 搭配的正确打开方式。