0、引言
在前面两篇文章已经对数据环境搭建、数据批量写入库中进行了较为详细的讲解。因此,基于前两篇文章内容的基础上,本文主要从数据库中批量数据读取操作进行梳理讲解。
嵌入式数据库SQLite 3配置使用详细笔记教程_sqlite3-CSDN博客
SQLite 3 优化批量数据存储操作---事务transaction机制_sqlite3 事务-CSDN博客
数据库,顾名思义,是一个数据存储的容器、装置,既能存储用户的数据,也能将存储的数据读取出来处理。但是,要怎么做才能把数据读取出来,又能保证效率呢?基于这个问题,本文将对sqlite3_stmt机制进行说明,并实现数据的读取。
1、sqlite3_stmt机制
sqlite3_stmt 是 SQLite 数据库在 C 语言接口中使用的一个关键数据结构,它代表了一个“准备语句对象”(prepared statement object),也是一个预编译的 SQL 语句,预编译 SQL 语句可以提高执行效率并防止 SQL 注入攻击。这个对象是对 SQL 语句的一种封装,该 SQL 语句已经被编译成字节码形式,可以直接由 SQLite 的虚拟机执行。
在 SQLite 中,预编译 SQL 语句有如下的几个优点:
①、性能提升:预编译的语句可以被数据库引擎更快地执行,因为编译步骤已经在准备阶段完成了。
②、安全性:使用预编译语句可以有效防止 SQL 注入攻击,因为参数值在执行时是分开绑定的,不会与 SQL 代码混合。
③、减少内存占用:对于重复执行的 SQL 语句,预编译可以减少内存的使用,因为相同的 SQL 代码只需要编译一次。
使用sqlite3_stmt基本步骤如下:
(1)、打开 SQLite 数据库连接(使用 sqlite3_open() 函数)。
(2)、准备 SQL 语句(使用 sqlite3_prepare_v2() 或 sqlite3_prepare16_v2() 函数)。
(3)、绑定参数(如果有的话,使用 sqlite3_bind_*() 系列函数)。
(4)、执行 SQL 语句(使用 sqlite3_step() 函数)。
(5)、处理结果集(如果有的话,使用 sqlite3_column_*() 系列函数)。
(6)、重置或清理 SQL 语句(使用 sqlite3_reset() 或 sqlite3_finalize() 函数)。
(7)、关闭数据库连接(使用 sqlite3_close() 函数)。
如果只是读取一条数据时,使用sqlite3_exec和sqlite3_stmt效率是一样的,但是当涉及到大批量的操作时,此前使用的sqlite3_exec就会不是最优选择了,因此推荐使用sqlite3_stmt机制。
2、常用API说明
从上图所示的Sqlite3的官方网站查询的数据信息可以看到,sqlite3_stmt相关的API有50多个。因此本文这一部分将对sqlite3_stmt常用的API进行介绍。
①、sqlite3_prepare_v2
int sqlite3_prepare_v2(sqlite3 *db, /* 数据库连接 */const char *zSql, /* SQL 文本 */int nByte, /* SQL 文本的最大长度,-1 表示自动计算长度 */sqlite3_stmt **ppStmt, /* 输出参数,指向预编译语句对象的指针 */const char **pzTail /* 输出参数,指向 SQL 文本中未处理部分的指针 */
);//成功返回SQLITE_OK (0),失败返回错误码
sqlite3_prepare_v2() 函数的主要用途是将 SQL 文本编译成一个可以执行的预编译语句对象。预编译语句在执行前可以进行参数绑定,这样可以提高执行效率并防止 SQL 注入攻击。这个函数是 sqlite3_prepare() 的改进版本,提供了更多的功能和更好的错误报告。
②、sqlite3_step
int sqlite3_step(sqlite3_stmt *pStmt);
//pStmt:一个指向预编译语句对象(sqlite3_stmt)的指针。//返回值:
//SQLITE_ROW(1):如果执行成功并且结果集中还有更多的行。
//SQLITE_DONE(100):如果执行成功并且所有行都已经被处理完毕。
//如果发生错误,返回错误码
sqlite3_step用于执行一个预编译的 SQL 语句,这个函数会执行语句,并将结果集推进到下一行(如果有的话)。当所有行都被处理完毕时,sqlite3_step() 返回 SQLITE_DONE。
③、sqlite3_bind_text
int sqlite3_bind_text(
sqlite3_stmt *stmt, /*stmt:一个指向预编译语句对象(sqlite3_stmt)的指针。*/
int idx, /*idx:占位符的索引,从 1 开始计数。*/
const char *zData, /*zData:要绑定的文本数据的指针。*/
int nData, /*nData:文本数据的长度(以字节为单位)。如果设置为 -1,SQLite 会自动计算文本的长度。*/
void (*xDel)(void *) /*一个可选的删除函数,用于在 sqlite3_finalize() 调用时释放 zData 指向的内存。如不需要释放内存,可以传递SQLITE_STATIC 或 NULL。*/
);//成功返回SQLITE_OK (0),失败返回错误码
sqlite3_bind_text用于将文本类型的参数绑定到一个预编译的 SQL 语句(sqlite3_stmt)中的占位符。这个函数允许安全地将变量值插入到 SQL 语句中,从而避免 SQL 注入攻击。
④、sqlite3_column_int64
int64_t sqlite3_column_int64(sqlite3_stmt *stmt, int iCol);
//stmt:一个指向预编译语句对象(sqlite3_stmt)的指针。
//iCol:要获取的列的索引,从 0 开始计数。//成功返回指定列的 int64_t 类型的值,失败返回0
sqlite3_column_int64用于从结果集中获取一个 int64_t 类型的列值。这个函数通常与 sqlite3_step() 函数一起使用,用于遍历查询结果。
⑤、sqlite3_column_text
const unsigned char *sqlite3_column_text(sqlite3_stmt *stmt, int iCol);
//stmt:一个指向预编译语句对象(sqlite3_stmt)的指针。
//iCol:要获取的列的索引,从 0 开始计数。//成功,返回指向指定列文本值的指针,失败返回NULL
sqlite3_column_text用于从结果集中获取一个文本类型的列值。这个函数通常与 sqlite3_step() 函数一起使用,用于遍历查询结果。
提醒:返回的指针指向的是 SQLite 内部的数据结构,不能直接修改或释放这部分内存。当 sqlite3_stmt 对象被销毁(例如通过调用 sqlite3_finalize())时,这部分内存也会被自动释放。
⑥、sqlite3_column_double
double sqlite3_column_double(sqlite3_stmt *stmt, int iCol);
//stmt:一个指向预编译语句对象(sqlite3_stmt)的指针。
//iCol:要获取的列的索引,从 0 开始计数。//成功,返回返回指定列的 double 类型的值,失败返回0.0
sqlite3_column_double用于从结果集中获取一个double类型的列值。这个函数通常与 sqlite3_step() 函数一起使用,用于遍历查询结果。
⑦、sqlite3_finalize
int sqlite3_finalize(sqlite3_stmt *stmt);
//stmt:一个指向预编译语句对象(sqlite3_stmt)的指针。//成功返回SQLITE_OK (0),失败返回错误码
用于清理和释放一个预编译的 SQL 语句对象(sqlite3_stmt)。
3、程序示例
在编写此参考程序示例时,使用的是之前存储下来的一个数据库中的一个表,该表的格式如下:
根据上图所示的内容,可以看到,该表中一共有6个数据,3种数据类型,分别是integer、text和real类型。接下来将对表中的数据进行读取解析并可视化呈现出来。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sqlite3.h>typedef struct {long long store_time;char name[20];double detect_time;float x;float y;float z;
}user_data_t;/*** @brief 读取Sqlite数据库中的xxx数据* @param db:数据库文件描述符* @param data:存储读取出来的数据* @retval 成功返回实际读取的数据个数,失败返回-1*/
int sqlite3_read_xxx_data(sqlite3* db, user_data_t* data, int max_count)
{if (db == NULL) {DBUG_SHOW(DBUG_ERROR, "%s Variable db is NULL!\n", __FUNCTION__);return -1;}char sql_cmd[] = "SELECT * FROM detect;";int ret = 0;sqlite3_stmt* stmt = NULL;const char* err_msg = NULL;ret = sqlite3_prepare_v2(db, sql_cmd, -1, &stmt, NULL);if (ret != SQLITE_OK) {DBUG_SHOW(DBUG_ERROR, "Prepare statement err: %s\n", sqlite3_errmsg(db));return -1;}int count = 0;while ((ret = sqlite3_step(stmt)) == SQLITE_ROW && count < max_count) {data[count].store_time = sqlite3_column_int64(stmt, 0);strncpy(data[count].name, (const char*)sqlite3_column_text(stmt, 1), sizeof(data[count].name) - 1);data[count].name[sizeof(data[count].name) - 1] = '\0';data[count].detect_time = sqlite3_column_double(stmt, 2);data[count].x = sqlite3_column_double(stmt, 3);data[count].y = sqlite3_column_double(stmt, 4);data[count].z = sqlite3_column_double(stmt, 5);count++;}if (ret != SQLITE_DONE) {err_msg = sqlite3_errmsg(db);if (strcmp(err_msg, "another row available") == 0) {DBUG_SHOW(DBUG_WARN, "This table has another row data available!\n");}else {DBUG_SHOW(DBUG_ERROR, "Fetch data err: %s\n", err_msg);sqlite3_finalize(stmt);return -1;}}sqlite3_finalize(stmt);return count;
}int main(void)
{sqlite3* read_db = NULL;char filename[256] = "2024081214134452.db";database_init(&read_db, filename);detect_database_t* data = malloc(sizeof(detect_database_t) * 512);int ret = sqlite3_read_xxx_data(read_db, data, 512);printf("data num : %d\n", ret);for (int i = 0; i < ret; i++) {printf(".......................................\n");printf("store_time:%lld\n", data[i].store_time);printf("name:%s\n", data[i].name);printf("detect_time:%.3f\n", data[i].detect_time);printf("x:%.3f\n", data[i].x);printf("y:%.3f\n", data[i].y);printf("z:%.3f\n", data[i].z);}free(data);while (1);
}
4、程序效果
在读取数据库前,通过SQLite Studio程序查看了数据库中,共存放了211条有效数据。
运行程序后,从可以看到,读取出了211条数据,且经过对比核验,读出的数据与库中的数据保持一致。