PAYATHON 2026

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 可能已经存在,导致第一条语句报错。是否删除或复用该表,应由业务逻辑决定。

备注:内容仅供参考。