sqlite3_prepare_v2() 接收多条 SQL 时,如何遍历第二条查询的结果?
结论
需要再次调用 sqlite3_prepare_v2(),将 pzTail 指向的剩余 SQL 编译成新的 sqlite3_stmt,再循环调用 sqlite3_step() 读取结果。
sqlite3_prepare_v2() 每次只编译输入字符串中的第一条 SQL。当前 ppStmt 对应的是 CREATE TEMPORARY TABLE ...,所以 sqlite3_step(ppStmt) 返回 SQLITE_DONE(101)说明建表语句已执行完毕,不能用这个返回码判断第二条查询是否为空。
为什么第二条查询没有执行
这段 SQL 包含两条独立语句:
CREATE TEMPORARY TABLE T1 AS SELECT * FROM TABLE1;
SELECT * FROM T1;
第一次调用 sqlite3_prepare_v2() 后:
ppStmt保存第一条语句的编译结果。pzTail指向尚未编译的剩余文本,也就是第二条查询所在的位置,前面可能有空白。- 调用
sqlite3_step(ppStmt)只会执行第一条语句。
pzTail 指向剩余 SQL。要编译其中的语句,需要将它传给 sqlite3_prepare_v2()。
执行第二条查询并遍历结果
下面的示例处理问题中的两条语句,假设 db 已经打开、TABLE1 存在,且当前连接中没有同名临时表 T1:
#include <sqlite3.h>
#include <cstdio>
#include <string>
int queryTemporaryTable(sqlite3* db) {
const std::string sql =
"CREATE TEMPORARY TABLE T1 AS SELECT * FROM TABLE1; "
"SELECT * FROM T1";
sqlite3_stmt* stmt = nullptr;
const char* tail = nullptr;
// 1. 编译并执行 CREATE TEMPORARY TABLE。
int rc = sqlite3_prepare_v2(
db, sql.c_str(), -1, &stmt, &tail);
if (rc != SQLITE_OK) {
std::fprintf(stderr, "prepare CREATE: %s\n",
sqlite3_errmsg(db));
return rc;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
std::fprintf(stderr, "execute CREATE: %s\n",
sqlite3_errmsg(db));
sqlite3_finalize(stmt);
return rc;
}
rc = sqlite3_finalize(stmt);
stmt = nullptr;
if (rc != SQLITE_OK) {
return rc;
}
// 2. 编译 tail 指向的 SELECT。
rc = sqlite3_prepare_v2(db, tail, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
std::fprintf(stderr, "prepare SELECT: %s\n",
sqlite3_errmsg(db));
return rc;
}
// 3. 每次 SQLITE_ROW 对应一行结果。
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
const int columnCount = sqlite3_column_count(stmt);
for (int i = 0; i < columnCount; ++i) {
if (i > 0) {
std::printf("\t");
}
if (sqlite3_column_type(stmt, i) == SQLITE_NULL) {
std::printf("NULL");
} else {
const unsigned char* value =
sqlite3_column_text(stmt, i);
std::printf("%s", value
? reinterpret_cast<const char*>(value)
: "");
}
}
std::printf("\n");
}
if (rc != SQLITE_DONE) {
std::fprintf(stderr, "execute SELECT: %s\n",
sqlite3_errmsg(db));
sqlite3_finalize(stmt);
return rc;
}
return sqlite3_finalize(stmt);
}
示例将非 NULL 列转换成文本输出,以展示如何遍历结果。实际业务中,可以根据列类型使用 sqlite3_column_int()、sqlite3_column_int64()、sqlite3_column_double() 或 sqlite3_column_blob() 等接口读取数据。
返回码和资源管理
执行时需要区分以下返回码:
sqlite3_prepare_v2()返回SQLITE_OK:编译成功,不代表语句已经执行。sqlite3_step()返回SQLITE_ROW:当前有一行数据可读。sqlite3_step()返回SQLITE_DONE:当前语句执行结束。- 其他返回码:需要按具体错误处理,可通过
sqlite3_errmsg(db)查看错误信息。
对第二条 SELECT 而言,如果第一次 sqlite3_step() 就返回 SQLITE_DONE,说明这条查询没有返回任何行。
pzTail 指向原始 SQL 字符串内部,没有独立的存储空间。用它编译后续语句前,必须保证原始字符串仍然有效,且没有发生会使指针失效的修改。每个成功创建的 sqlite3_stmt 都应调用 sqlite3_finalize() 释放。
如果 SQL 语句的数量不固定,可以沿着 pzTail 逐条编译、执行和释放。通用循环还要处理编译成功但语句指针为 nullptr 的情况,例如剩余文本只有空白或注释。
在同一连接中再次运行示例时,临时表 T1 可能已经存在,导致第一条语句报错。是否删除或复用该表,应由业务逻辑决定。
备注:内容仅供参考。
