当前位置: 首页 > news >正文

数据库(sqlite)基本操作

数据库(sqlite)

一:简介:

为什么需要单独的数据库来进行管理数据?

  1. 数据的各种查询功能
  2. 数据的备份和恢复
  3. 花大量时间在文件数据的结构设计和维护上
  4. 要考虑多线程对数据的操作会涉及到同步问题,会增加很多额外工作
sqlite

(1)概念

SQLite是一个开源的轻量级关系数据库管理系统,它的不同之处在于它是以库的形式提供的,这意味着它是一个零配置、无需安装和管理的数据库引擎(一个Sqlite数据库,就是一个单独的文件)。它不是一个独立的服务进程,而是直接集成到应用程序中。SQLite存储整个数据库(定义、表、索引和数据)为单个跨平台的文件(.db文件),在多个系统和程序语言之间提供极致的可移植性。

注意:sqlite所有操作都是在本地直接通过对数据库文件的读写来完成的

二:sqlite安装(linux下)

1、 安装sqlite3
sudo apt install sqlite3
3、退出数据库操作终端命令
ctrl +d 
或
输入 .quit 或  .exit
4、关于数据库的一些核心概念

(1)数据库

  • 安装的sqlite3本身是一个数据库软件,通过该软件可以创建和管理多个以二维表作为数据存储手段的数据库,比如学校数据库、迷宫数据库、电影院数据库

(2)表(二维表)

  • 概念:二维表(简称表)是保存某一类数据的具体载体。一般一个数据库可以有多张二维表,每张二维表可以存储一类的数据。比如学生表(保存学生类的数据,一行数据就是一个学生),成绩表、老师表。

  • 例子:迷宫数据库

    • 应该有那些表

      • buff表

      • 敌人表

      • 地图表

        地图编号row_indexcol_indexvalue
        1001
        1011
        1021
        省略了17行
        1101
        1113
        1120
      • 玩家表:

        玩家编号玩家名称xybuff表步数地图编号
        1张三1213231
        2李四153453

(3)一行数据

  • 我们一般会将一张表的一行作为完整的一个数据。

三:表的操作

1、创建一张表
语法:
CREATE TABLE 二维表名{列1名称 数据类型  该列的特点,2名称 数据类型  该列的特点,...列n名称 数据类型  该列的特点
};--  表的最后一列末尾不加逗号
例子:构建一张学生表
create table student (id integer primary key autoincrement,name text not null,age integer,email text not null unique
);-- 创建带时间的表
CREATE TABLE events (id INTEGER PRIMARY KEY,name TEXT NOT NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入带时间的数据
INSERT INTO events (name, created_at) VALUES ('Event 2', DATETIME('now'));

其中:id为列的名称,primary key表示该列为主要列,一张表最多只有一列是主要列。一般表示该列是用于区分每一行内容的。比如每个人都有身份证号码,那么该号码就可以作为人这张表的primary key列。AUTOINCREMENT表示该列自动填充,从1开始依次递增。也就是第一行内容id为1,第二行内容id为2。INTEGER表示数据类型。数据库有很多类型。但大家只需要关注INTEGER TEXT REAL 整数、字符串、浮点数三种数据类型即可。数据类型和关键字不区分大小写。这几种只有浮点数确定是8个字节,其他数据内存大小是由数据的本身大小来决定的。

每列的特点(都是可选,根据实际需求来定):

**PRIMARY KEY:**将该列作为该表的主要参考列(简称主键),一般我们会将编号列作为主键。该列的每一个数据不能重复,一般会配合AUTOINCREMENT来使用,会自动在给表插入新数据时,该列编号自动填写并为上一个数据编号+1

NOT NULL 表示该列数据不能为空。

UNIQUE表示该列数据不能出现重复的。

DEFAULT 数据: 表示该列可以有一个默认数据,比如default 0 表示默认数据为0,前提是数据类型是 数字,如果是文本则需要加一对引号

2、列出所有表
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;

注意

(1)sqlite-master

sqlite_master是SQLite数据库中的一个内置表,每个SQLite数据库文件都包含这个表。sqlite_master表存储了数据库的元数据,包括数据库中存在的所有表格、索引、视图以及触发器的定义信息。通常不需要更改这张表。也可以用sql语句来查看这张表里面的内容。比如SELECT type, name, sql FROM sqlite_master WHERE type='table';

sqlite_master表的结构大概如下:

  • type: 对象的类型(例如,‘table’、‘index’、‘view’、‘trigger’)。
  • name: 对象的名称。
  • tbl_name: 对象相关联的表的名称(对于表本身,nametbl_name字段将相同)。
  • rootpage: 对象在数据库文件中的起始页码。
  • sql: 创建该对象的原始SQL语句。

(2)sqlite_sequence

在SQLite数据库中,sqlite_sequence是一个内部系统表,用于跟踪自动增长(AUTOINCREMENT)字段的当前最大值。当你在表定义中为某个列使用了INTEGER PRIMARY KEY AUTOINCREMENT属性时,SQLite会使用sqlite_sequence表来存储该列最近分配的最大整数值。这样,当新记录被插入到表中时,SQLite就可以为带有AUTOINCREMENT属性的列自动分配一个比之前所有记录都大的唯一标识符。

sqlite_sequence表有两个字段:

  • name:这一列存储的是表的名称,该表含有一个AUTOINCREMENT字段。
  • seq:这一列存储的是该表中最后一个被分配的AUTOINCREMENT值。
3、查看表结构
PRAGMA table_info(表名);// 只列出自己表
sqlite> .tables
s2       student
4、删除表
DROP TABLE IF EXISTS 表名;

四:使用工具连接

SqliteBrowser

sudo apt-get install libsqlite3-dev
step3:安装Sqlite3图形化管理界面DB Browser for SQLite(用户可选择性安装):sudo apt-get install sqlitebrowser
step4:检查是否安装成功,如果成功,输入以下命令可显示SQLite3版本信息。

五:数据的操作

sqlite推荐用单引号来包围字符串,双引号在不同版本中会有冲突

1:增加数据
-- 新增数据
-- INSERT INTO student (id, name, age, gender) values (2,'tom',26,'male')
INSERT INTO student ( name, age, gender) values ( 'lili',25,'female');
-- INSERT INTO student values ( 5, 'lili',22,'female')
-- Result: table student has 4 columns but 3 values were supplied
2:修改数据
-- 修改数据:
-- UPDATE student SET name='张三', age=50, gender = '女' WHERE id = 8-- UPDATE student SET age=18 WHERE id = 1 or id = 3 or id = 5-- UPDATE student SET gender='男' WHERE id = 1 and id = 3-- 可以匹配多个id
-- UPDATE student SET age=3 WHERE id in (1,3,5, 11)
3:删除数据
-- 删除数据
-- 同样的,条件是可以跟多个的,也可以用or 或 and
-- delete from student where id = 3
-- 不跟条件,是清空表里的数据
-- delete from student
4:查找数据
-- 删除数据
-- delete from student where id = 3
-- 不跟条件,是清空表里的数据
-- delete from student-- 查询操作:
-- * 代表显示所有的列
-- select * from student
-- select id, name , age, gender from student-- 查询指定id的数据
--select * from student where id in (1,3,5,7,9);--  范围查询:
-- select * from student where age >= 20 and age <= 25-- select * from student where age between 20 and 25-- NULL != NULL
-- select * from student where gender is NULL-- 查询结果,可以再处理,也可以使用别名
-- select id as "学号" , name as 名字, age+10 年龄 from student where gender is not NULL-- 模糊查询:
/*_ 代表任意一个字符:% 任意个,任意字符
*/
-- select * from student where name like '%强%'-- 查询含有特殊字符的数据,需要使用到转义符
-- select * from student where name like '%\_%' ESCAPE '\'-- 分页查询:
-- select * from student limit 0, 3   -- 	第一页
-- select * from student limit 3, 3   -- 	第二页
-- select * from student limit 6, 3   -- 	第三页/*limit x, y;当前页:currentPage  = 2页大小:pageSize = 3x = (currentPage-1)*pageSzie;y = pageSize;
*/

练习:

1:分别查询出男生的id,名字和女生的id,名字2:查询年龄大于20岁的人员信息3:查询出年龄在15-25之间的,女性的信息4:查询出id是 13568 男性人员的信息5:查询出,联系电话为空的人员的信息6:查询出,所有的名字带 ‘丽’ 的人员的信息
  • 答案:
select id, name from student where gender = 'F' select * from student where age > 20select * from student where age between 15 and 25 and gender = 'female';select * from student where gender = 'male' and id in (1,3,5,6,8)select * from student where tel is NULL;select * from student where name like '%丽%'
  • 查询的结果排序:
-- 排序规则默认是:asc 数字是从小到大,字母是字典顺序  desc 是倒序
-- select * from student order by name asc-- 排序的规则后面,可以跟多个字段。
select * from student order by age desc, name desc

练习:

  • 找出年龄最大的前3个学生的信息:

  • 找出最大的年龄

select * from student order by age desc limit 0, 3select age from student order by age desc limit 0, 1

六:常见组函数:

-- 求最大值
-- select max(age) from student-- 求最小值 
-- select min(age) from student-- 求平均数
-- select avg(age) from student-- 计数
-- select count(id) from student-- 求总和
select sum(age) from student

七:C 程序中使用sqlite3

1:使用步骤

  • 安装:使用sudo apt install libsqlite3-dev让Linux安装sqlite3开发库
  • 引入:c文件引入sqlite3.h头文件
  • 打开数据库:使用sqlite3_open函数打开数据库
  • 执行:使用sqlite3_exec来执行sql语句,比如创建表、增删查改数据等
  • 关闭数据库:使用sqlite3_close来关闭数据库
  • 编译:代码写好后进行gcc编译时,需要加上-lsqlite3参数。比如gcc xxx.c -o sqldemo -lsqlite3

2:操作函数

api大全:https://sqlite.readdevdocs.com/cintro.html

(1)sqlite3_open
  • 语法

    int sqlite3_open(const char *filename, sqlite3 **ppDb);例子:
    sqlite3 *db;
    //链接数据库
    int rc = sqlite3_open("stu.db",&db);
    
  • 功能:链接数据库

  • 参数

    • filename: 数据库文件的名称。如果文件不存在,SQLite 会尝试创建它。
    • ppDb: 是指向 sqlite3 结构体指针的指针。当函数执行成功后,这个指针会被设置为指向一个打开的数据库实例
  • 返回值

    • 函数返回一个整数状态码。SQLITE_OK (0) 表示成功,其他任何返回值都表示失败
(2)sqlite3_exec
  • 功能:执行sql语句

  • 语法-

    int sqlite3_exec(sqlite3*,                                  /* 打开的数据库指针 */const char *sql,                           /* sql语句 */int (*callback)(void*,int,char**,char**),  /* 回调函数:返回查询结果 */void *pArg,                                /* 传递给回调函数的参数,可以设置为NULL */char **errmsg                              /* 错误信息 */
    );/* 回调函数说明:*/
    sqlite3_callback 通常指的是传递给 sqlite3_exec() 函数的第三个参数,这是一个用户定义的回调函数。当 sqlite3_exec() 执行一个 SQL 查询(如 SELECT 语句)并返回结果行时,它会调用这个回调函数来处理每一行的数据。
    回调函数的签名是固定的,以便 SQLite 能够正确地调用它。这个签名通常如下:
    typedef int (*sqlite3_callback)(void*,    /* 用户提供的上下文指针,对应于 sqlite3_exec() 的第四个参数 */int,      /* 结果集中的列数 */char**,   /* 指向结果行中各列数据的指针数组 */char**    /* 指向结果行中各列名称的指针数组 */
    )
    
  • 参数

    • 第一个参数是一个打开的数据库实例,之前由 sqlite3_open 打开。
    • 第二个:sql 是要执行的SQL语句字符串。可以包含一个或多个由分号分隔的SQL命令。
    • 第三个:callback是一个指向函数的指针,这个函数会被每一行结果调用一次。如果不需要处理结果集,则可以为NULL。
      • 该回调函数的签名应该符合int callback(void *NotUsed, int argc, char **argv, char **azColName),其中:
        • NotUsed 是sqlite3_exec中用户提供的第四个参数。
        • argc 表示结果集中的列数。
        • argv 是一个字符串数组,包含每一列的值。
        • azColName 是一个字符串数组,包含每一列的名称。
    • 第四个参数: pArg
      • 这是一个用户提供的上下文指针,它将被传递给回调函数(如果提供了回调函数的话)。
      • 您可以使用这个指针来传递任何您希望在回调函数中访问的数据或状态信息。
      • 如果不需要传递任何上下文信息给回调函数,可以将此参数设置为 NULL
    • errmsg 如果不为NULL,任何错误信息都会被写入到它指向的位置。使用 sqlite3_free() 来释放这个错误信息的内存
  • 返回值

    • 返回 SQLITE_OK (0) 表示成功,其他值表示失败。失败时,如果 errmsg 不为NULL,它将指向一个错误消息
(3)sqlite3_free
  • 功能:释放内存,一般指sql语句执行后分配给errmsg的内存。

  • 语法

    void sqlite3_free(void*);例子:sqlite3_free(err_message);
    //err_message 是执行sql失败之后的错误信息
    
  • 参数

    • 接受一个指针,该指针必须是由sqlite分配的内存块的指针,通常是在之前的 sqlite3_exec 调用中通过 errmsg 返回的错误消息.如果sql语句执行成功,一般不用执行该函数
  • 返回值:无

(4)sqlite3_close
  • 功能:关闭一个打开的数据库连接

  • 语法

    int sqlite3_close(sqlite3*);
    例子:
    sqlite3_close(db);
    
  • 参数

    • 接受一个指向 sqlite3 结构体的指针,这个指针代表一个打开的数据库实例
  • 返回值

    • 如果成功关闭数据库,返回 SQLITE_OK。如果作为参数传递给 sqlite3_close 的数据库实例有尚未完成的操作(例如,未完成的准备语句、未关闭的BLOB句柄等),则返回 SQLITE_BUSY
(4)sqlite3_changes
  • 功能:得到数据库连接上次调用sqlite3_exec以来被修改、插入或删除的行数

  • 语法

    int  sqlite3_changes(db);
    
  • 参数

    • db:接受一个指向 sqlite3 结构体的指针,这个指针代表一个打开的数据库实例
  • 返回值

    • 得到数据库连接上次调用sqlite3_exec以来被修改、插入或删除的行数,如果没有更改,则返回0
  • 代码示例:新增数据:

    #include <stdio.h>
    #include <stdbool.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sqlite3.h>
    int main(int argc, char *argv[]) {//char sql[] = "insert into student (name,password, age , gender) values ('小丽丽','1234',19,'女')";//char sql[] = "update student set age = 88 where id = 1 or id = 3";char sql[] = "delete from student where id = 3";// 1: 打开数据库sqlite3 *db;//链接数据库int rc = sqlite3_open("school.db",&db);if(rc != 0){perror("数据库,打开失败!");exit(EXIT_FAILURE);}char *errmsg = 0;// 2: 执行sqlrc = sqlite3_exec(db,                                  /* 打开的数据库指针 */sql,                           	   /* sql语句 */NULL,						  		   /* 回调函数:返回查询结果 */NULL,                                /* 传递给回调函数的参数,可以设置为NULL */&errmsg                              /* 错误信息 */);if(rc == SQLITE_OK ){int rows = sqlite3_changes(db);printf("sql执行成功!影响数据行数:%d\n", rows);}else {// 打印错误信息:fprintf( stderr, "SQL错误:%s\n", errmsg );sqlite3_free(errmsg);}// 3: 关闭数据库sqlite3_close(db);return 0;
    }
    
  • 查询数据:

    #include <stdio.h>
    #include <stdbool.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sqlite3.h>// 用来显示数据的回调函数
    int showData(void*,int,char**,char**);int loginState = 0;bool login(char* name, char* password){//char sql[] = "select * from student where name = '' and password = '' ";char sql[128];sprintf( sql, "select * from student where name = '%s' and password = '%s' ", name, password );// 1: 打开数据库sqlite3 *db;//链接数据库int rc = sqlite3_open("school.db",&db);if(rc != 0){perror("数据库,打开失败!");exit(EXIT_FAILURE);}char *errmsg = 0;// 2: 执行sqlrc = sqlite3_exec(db,                                  /* 打开的数据库指针 */sql,                           	   /* sql语句 */showData,						  	   /* 回调函数:返回查询结果 */NULL,                                /* 传递给回调函数的参数,可以设置为NULL */&errmsg                              /* 错误信息 */);if(rc == SQLITE_OK ){printf("sql执行成功!\n");}else {// 打印错误信息:fprintf( stderr, "SQL错误:%s\n", errmsg );sqlite3_free(errmsg);}// 3: 关闭数据库sqlite3_close(db);
    }int main(int argc, char *argv[]) {char name[] = "abcd";char password[] = "' or '1'='1";login(name, password);if(loginState){puts("登录成功");}else {puts("用户名或密码错误!");}return 0;
    }int showData(void* no_used,int cols ,char** col_values ,char** col_names){printf("总列数是:%d\n", cols);puts("--------------------------------");for(int i = 0; i < cols; i++){//loginState = 1;printf("列名:%s , 值:%s \n", col_names[i], col_values[i]);//return 0;}puts("--------------------------------");return 0;
    }
    

这样使用字符串,拼接方式,得到的sql语句,有注入的风险。

我们应该避免这样的情况发生。

八:sql的进阶操作:

当我们以拼接字符中的方式创建SQL语句,其实是有SQL注入风险的。我们应该避免这种情况。

1:使用预编译,对sql进行处理:
  • 语法说明:

  • sqlite3_prepare_v2() 参数说明:

    int sqlite3_prepare_v2(sqlite3 *db,            /* Database handle */const char *zSql,       /* SQL statement, UTF-8 encoded */int nByte,              /* Maximum length of zSql in bytes. */sqlite3_stmt **ppStmt,  /* OUT: Statement handle */const char **pzTail     /* OUT: Pointer to unused portion of zSql */
    );
    
    1. sqlite3 *db
      • 该参数为指向 sqlite3 类型的指针,代表了一个已经打开的 SQLite 数据库连接。在调用 sqlite3_prepare_v2() 之前,你需要使用 sqlite3_open() 或者 sqlite3_open_v2() 函数来打开数据库,从而得到这个数据库连接句柄。只有借助有效的数据库连接,才能对相应的数据库开展操作。
    2. const char *zSql
      • 此参数是一个指向以 null 结尾的字符串的指针,该字符串包含了要编译的 SQL 语句。这个 SQL 语句可以是 SELECTINSERTUPDATEDELETE 等各类 SQL 命令。需要注意的是,SQL 语句里可以包含参数占位符(如 ? 或者 :name),这有助于后续绑定具体的值。
    3. int nByte
      • 该参数指定了 zSql 中 SQL 语句的最大字节数。若 nByte 为负数,那么 sqlite3_prepare_v2() 函数会把 zSql 当作以 null 结尾的字符串来处理,直至遇到字符串结束符 '\0' 为止。若 nByte 为正数,函数会处理 zSql 中的前 nByte 个字节。
    4. sqlite3_stmt **ppStmt
      • 这是一个指向 sqlite3_stmt 指针的指针。sqlite3_stmt 代表了一个准备好的语句对象,在调用 sqlite3_prepare_v2() 之后,该函数会把编译好的准备好的语句对象的地址存储到 *ppStmt 中。后续你可以使用这个准备好的语句对象来执行 SQL 语句、绑定参数等操作。在使用完毕后,要调用 sqlite3_finalize() 函数来释放这个准备好的语句对象所占用的资源。
    5. const char **pzTail
      • 这是一个指向指针的指针,用于返回 SQL 语句中未处理的部分。在 zSql 中可能包含多条 SQL 语句,当 sqlite3_prepare_v2() 编译完第一条 SQL 语句后,会把剩余部分的起始地址存储到 *pzTail 中。若 zSql 中只有一条 SQL 语句,那么 *pzTail 会指向 SQL 语句结束符 '\0' 之后的位置
  • 参数详细说明:

    const char *sql = "SELECT * FROM student where id = ? or name = ?";
    // 1:对sql进行预编译
    sqlite3_prepare_v2(db,      /* 数据库连接句柄 */sql,		/* 输入的 SQL 语句 */-1,       /* SQL 语句的最大字节数 */&stmt,    /* 指向准备好的语句对象的指针 */NULL);    /* 指向 SQL 语句中未处理部分的指针 */// 2:对sql中的?号占位符进行参数绑定
    int id = 3;
    sqlite3_bind_int(stmt, 1, id); // 绑定int值 char name[] = "tom";
    sqlite3_bind_text(stmt, 2 , name , -1, SQLITE_STATIC ); // 绑定字符串/*sqlite3_bind_text 函数的参数说明:1: sqlite3_stmt 变量 2: 占位符的位置,从1开始 3: 占位符对应的值4: 字符串的长度,-1代表系统自动运算 5: 有三个取值:5-1: SQLITE_STATIC    :在预处理的整个生命周期内都是有效的,无需复制该值,因它知在执行期间该值不会变或释放5-2: SQLITE_TRANSIENT :表示提供的值在语句执行期间有效,但可能在语句执行后被释放或修改。SQLite 通常会复制这个值以确保安全5-3: SQLITE_DYNAMIC	  :表示值是通过malloc()获得的且SQLite在无需使用时该释放它。通常用于希望SQLite管理内存时
    */
    // 3:注意要使用 sqlite3_step(stmt); 执行sql -----------------
    sqlite3_step(stmt);	
    
  • sqlite3_step() 函数 返回值说明;

    • 函数成功状态码:

      • SQLITE_ROW(100):表示查询结果集中存在下一行数据。当执行 SELECT 语句时,如果有结果行,sqlite3_step 会返回此值,你可以通过 sqlite3_column_ 系列函数来获取该行数据的各个列值*。
      • SQLITE_DONE(101):意味着 SQL 语句执行完毕。对于非查询语句(如 INSERT、UPDATE、DELETE),当操作完成时返回此值;对于查询语句,当遍历完所有结果行后也会返回此值。
    • 错误状态码:

      • 有很多,从1~29 代表各种不同的错误(详细看官网)
  • 详解 sqlite3_step()SQLITE_ROWSQLITE_DONE


    一、函数原型
    int sqlite3_step(sqlite3_stmt* stmt);
    

    二、返回值核心含义
    返回值含义适用语句类型
    SQLITE_ROW当前有数据行可用(仅SELECT查询)SELECT 及其变种
    SQLITE_DONE语句执行完成(INSERT/UPDATE/DELETE成功,或SELECT结果集遍历结束)所有SQL语句

    三、SQLITE_ROW 详解

    1. 触发条件

    • SELECT查询结果集中还有未读取的行

    2. 典型用法

    sqlite3_stmt* stmt;
    sqlite3_prepare_v2(db, "SELECT id, name FROM users", -1, &stmt, NULL);// 循环读取每一行
    while (sqlite3_step(stmt) == SQLITE_ROW) {int id = sqlite3_column_int(stmt, 0);          // 读取第0列(id)const char* name = sqlite3_column_text(stmt, 1); // 读取第1列(name)printf("ID: %d, Name: %s\n", id, name);
    }
    

    3. 关键特性

    • 必须用while循环处理多行结果
    • 每次返回SQLITE_ROW时,需要用sqlite3_column_*函数读取当前行数据

    四、SQLITE_DONE 详解

    1. 触发场景

    语句类型含义
    SELECT结果集已全部遍历完成(没有更多数据行)
    INSERT/UPDATE/DELETESQL语句执行成功,数据已修改
    其他语句语句执行完成(如CREATE TABLE)

    2. 典型用法
    场景1:判断写操作成功

    // INSERT示例
    sqlite3_prepare_v2(db, "INSERT INTO logs(msg) VALUES('error')", -1, &stmt, NULL);
    if (sqlite3_step(stmt) == SQLITE_DONE) {printf("插入成功,影响行数:%d\n", sqlite3_changes(db));
    } else {printf("失败:%s\n", sqlite3_errmsg(db));
    }
    

    场景2:SELECT结束检测

    while (sqlite3_step(stmt) == SQLITE_ROW) { /* 读取数据 */ }
    // 循环退出时自动达到SQLITE_DONE状态
    printf("查询完成\n");
    

    五、对比案例
    案例1:SELECT查询
    sqlite3_stmt* stmt;
    sqlite3_prepare_v2(db, "SELECT * FROM products", -1, &stmt, NULL);int row_count = 0;
    while (sqlite3_step(stmt) == SQLITE_ROW) {printf("产品:%s\n", sqlite3_column_text(stmt, 1));row_count++;
    }
    printf("共查询到%d条数据\n", row_count);  // 循环退出时即SQLITE_DONE状态
    
    案例2:INSERT操作
    sqlite3_prepare_v2(db, "INSERT INTO orders VALUES(1001, 'book')", -1, &stmt, NULL);
    if (sqlite3_step(stmt) == SQLITE_DONE) {printf("订单创建成功!\n");
    } else {printf("错误码:%d\n", sqlite3_errcode(db));
    }
    

    六、特殊注意事项
    1. 必须检查返回值
      错误示例:

      sqlite3_step(stmt);  // ❌ 不检查返回值可能导致未知错误
      
    2. 重置语句
      如需复用语句对象,必须调用sqlite3_reset()

      sqlite3_reset(stmt);  // 重置到可执行状态
      sqlite3_clear_bindings(stmt);  // 可选:清除绑定参数
      
    3. 多语句执行
      批量执行SQL时,每次sqlite3_step()只推进一个语句:

      // 执行多条SQL
      sqlite3_prepare_v2(db, "INSERT INTO t1 VALUES(1); INSERT INTO t2 VALUES(2);", ...);
      while (sqlite3_step(stmt) == SQLITE_ROW) {}  // 忽略SELECT结果
      if (sqlite3_step(stmt) == SQLITE_DONE) {printf("所有语句执行完成\n");
      }
      

    七、流程图解
    SELECT
    INSERT/UPDATE/DELETE
    开始执行sqlite3_step
    语句类型
    返回SQLITE_ROW?
    读取当前行数据
    返回SQLITE_DONE
    执行修改
    成功?
    返回SQLITE_DONE
    返回错误码

    八、总结
    • SQLITE_ROW

      • SELECT查询的"数据就绪"信号
      • 必须配套使用sqlite3_column_*读取数据
      • 需要循环处理直到返回SQLITE_DONE
    • SQLITE_DONE

      • 所有语句的"执行完成"信号
      • 写操作的成功标志
      • SELECT查询的结束标志
  • 详解 sqlite3_changes()sqlite3_errmsg()


    1. sqlite3_changes():获取受影响的行数

    作用
    返回最近一次 INSERT/UPDATE/DELETE 操作影响的数据行数。

    函数原型

    int sqlite3_changes(sqlite3* db);
    

    关键特性

    • 立即生效:只反映最近一次操作的修改量
    • 作用范围:针对当前数据库连接(sqlite3* db
    • 返回值
      • 正整数:实际受影响的行数
      • 0:语句执行但未修改数据(如 UPDATE 条件不匹配)

    典型用法

    // 执行UPDATE操作
    sqlite3_exec(db, "UPDATE users SET status=1 WHERE age>18", NULL, NULL, NULL);// 获取影响行数
    int rows_updated = sqlite3_changes(db);
    printf("更新了%d条用户数据\n", rows_updated);
    

    注意事项

    • 不适用于 SELECT 语句(返回0)
    • 批量操作时只记录最后一条语句的影响

    2. sqlite3_errmsg():获取错误信息

    作用
    返回最近一次SQLite操作的错误描述(人类可读的字符串)。

    函数原型

    const char* sqlite3_errmsg(sqlite3* db);
    

    关键特性

    • 错误覆盖:包括预处理、执行、约束违反等所有阶段错误
    • 返回值
      • 错误描述字符串(如 "UNIQUE constraint failed"
      • 成功时为 "not an error"

    典型用法

    if (sqlite3_step(stmt) != SQLITE_DONE) {printf("操作失败:%s\n", sqlite3_errmsg(db));// 输出示例:"operation failed: UNIQUE constraint failed: users.email"
    }
    

    搭配函数

    int errcode = sqlite3_errcode(db);  // 获取错误码(如SQLITE_CONSTRAINT)
    

    3. 对比总结
    函数作用适用场景返回值类型
    sqlite3_changes()获取写操作影响行数INSERT/UPDATE/DELETE整数
    sqlite3_errmsg()获取错误描述任何操作失败时字符串
2:预编译使用:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>// 用来显示数据的回调函数
int showData(void*,int,char**,char**);bool login(char* name, char* password){char sql[] = "select * from student where name = ? and password = ? ";// 1: 打开数据库sqlite3 *db;//链接数据库int rc = sqlite3_open("school.db",&db);if(rc != 0){perror("数据库,打开失败!");exit(EXIT_FAILURE);}char *errmsg = 0;// 2: -------------------------------------------------// 2-1:执行sql的预编译:sqlite3_stmt* stmt;sqlite3_prepare_v2(db,            	/* Database handle */sql,       		/* SQL statement, UTF-8 encoded */-1,              	/* Maximum length of zSql in bytes. */&stmt,  			/* OUT: Statement handle */NULL     			/* OUT: Pointer to unused portion of zSql */);// 2-2: 对问号进行赋值:// int sqlite3_bind_int(sqlite3_stmt*, int, int);  // 绑定int值,使用的函数// 把name绑定到1号位置的问号sqlite3_bind_text( stmt ,1 , name , -1 , SQLITE_STATIC );sqlite3_bind_text( stmt ,2 , password , -1 , SQLITE_STATIC );// 2-3: 执行:bool f = false;if(sqlite3_step(stmt)== SQLITE_ROW){// 2-4: 得到查询的结果int id = sqlite3_column_int(stmt, 0);char name[32] = {0};strcpy(name, sqlite3_column_text(stmt, 1));char password[32] = {0};strcpy(password, sqlite3_column_text(stmt, 2));printf("id是:%d, 用户名是:%s , 密码:%s \n", id, name, password);f = true;}// 3: 关闭数据库sqlite3_close(db);return f;
}void showAll(){char sql[] = "select * from student ";// 1: 打开数据库sqlite3 *db;//链接数据库int rc = sqlite3_open("school.db",&db);if(rc != 0){perror("数据库,打开失败!");exit(EXIT_FAILURE);}char *errmsg = 0;// 2: -------------------------------------------------// 2-1:执行sql的预编译:sqlite3_stmt* stmt;sqlite3_prepare_v2(db,            	/* Database handle */sql,       		/* SQL statement, UTF-8 encoded */-1,              	/* Maximum length of zSql in bytes. */&stmt,  			/* OUT: Statement handle */NULL     			/* OUT: Pointer to unused portion of zSql */);// 2-3: 执行:while(sqlite3_step(stmt)== SQLITE_ROW){// 2-4: 得到查询的结果int id = sqlite3_column_int(stmt, 0);char name[32] = {0};strcpy(name, sqlite3_column_text(stmt, 1));char password[32] = {0};strcpy(password, sqlite3_column_text(stmt, 2));int age = sqlite3_column_int(stmt, 3);char gender[32] = {0};strcpy(gender, sqlite3_column_text(stmt, 4));printf("id是:%d, 用户名是:%s , 密码:%s  , 年龄:%d , 性别:%s\n", id, name, password, age, gender);}// 要释放stmt内存sqlite3_finalize(stmt);// 3: 关闭数据库sqlite3_close(db);}int main(int argc, char *argv[]) {
/*char name[] = "jack";char password[] = "loverose";bool f = login(name, password);if(f){puts("登录成功");}else {puts("用户名或密码错误!");}
*/showAll();return 0;
}

为什么在查询数据时使用 SQLITE_ROW 而不是 SQLITE_DONE


1. 核心区别
返回值含义适用场景
SQLITE_ROW当前有数据行可读取仅用于 SELECT 查询,表示结果集中有数据
SQLITE_DONE语句执行完成用于所有SQL语句,表示操作结束(无更多数据或操作完成)

2. 代码场景分析

您的代码是典型的 SELECT查询数据读取逻辑

if(sqlite3_step(stmt) == SQLITE_ROW) {  // ✅ 正确用法int id = sqlite3_column_int(stmt, 0);char name[32] = {0};strcpy(name, sqlite3_column_text(stmt, 1));// ...读取其他列...
}
为什么不能用 SQLITE_DONE
  1. 逻辑矛盾

    • SQLITE_DONE 表示"没有数据可读"或"操作完成"
    • 如果用它作为条件,代码块永远不会执行(因为当返回 SQLITE_DONE 时,已经无法读取数据)
  2. 执行流程

    • sqlite3_step(stmt) 首次调用时:
      • 如果有数据 → 返回 SQLITE_ROW
      • 如果无数据 → 直接返回 SQLITE_DONE
    • == SQLITE_DONE 判断会直接跳过数据读取

3. 正确模式对比
SELECT查询(必须用 SQLITE_ROW
// 正确写法:循环读取每一行
while (sqlite3_step(stmt) == SQLITE_ROW) {// 读取当前行数据int id = sqlite3_column_int(stmt, 0);// ...
}
// 退出循环时,说明已返回 SQLITE_DONE(数据读完)
INSERT/UPDATE/DELETE(用 SQLITE_DONE
// 正确写法:判断操作是否完成
if (sqlite3_step(insert_stmt) == SQLITE_DONE) {printf("插入成功,影响行数:%d\n", sqlite3_changes(db));
}

4. 底层原理
  • SQLITE_ROW 机制

    • SQLite 内部维护一个结果集游标
    • 每次 sqlite3_step() 推进游标到下一行
    • 返回 SQLITE_ROW 时,游标指向有效数据行
  • SQLITE_DONE 触发时机

    • SELECT:游标越过最后一行
    • INSERT/UPDATE/DELETE:语句执行完毕

5. 错误用法示例
// ❌ 错误写法:永远读不到数据
if (sqlite3_step(stmt) == SQLITE_DONE) {// 这里不会执行,因为:// 1. 如果有数据,先返回 SQLITE_ROW// 2. 如果无数据,条件成立但已无数据可读int id = sqlite3_column_int(stmt, 0); // 无效操作!
}

6. 完整查询流程
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, "SELECT id, name FROM users", -1, &stmt, NULL);// 方法1:单行查询(非循环)
if (sqlite3_step(stmt) == SQLITE_ROW) {printf("第一行数据:id=%d\n", sqlite3_column_int(stmt, 0));
}// 方法2:多行查询(循环)
while (sqlite3_step(stmt) == SQLITE_ROW) {printf("id=%d, name=%s\n", sqlite3_column_int(stmt, 0),sqlite3_column_text(stmt, 1));
}sqlite3_finalize(stmt);

7. 总结
  • SQLITE_ROW

    • SELECT查询的"数据就绪"信号
    • 唯一用于读取数据的判断条件
  • SQLITE_DONE

    • 所有SQL语句的"终止"信号
    • 用于确认操作完成,不能用于读取数据

关键记忆点

要读数据,等 ROW
操作结束,看 DONE

SQLite 结果集指针机制详解

1. 指针(游标)的工作方式

当执行 SELECT 查询时,SQLite 内部会创建一个结果集游标,其行为如下:

游标状态返回值数据访问能力
指向有效行SQLITE_ROW可通过 sqlite3_column_* 读取当前行
越过最后一行SQLITE_DONE无法再读取数据
2. 数据检索流程
首次调用sqlite3_step
有数据?
返回SQLITE_ROW,游标锁定当前行
返回SQLITE_DONE
用sqlite3_column_*读取数据
再次调用sqlite3_step
3. 关键区别
场景SQLITE_ROWSQLITE_DONE
游标位置停在有效数据行已越过结果集末尾
数据可读性可立即读取当前行无法读取任何数据
典型代码位置while(sqlite3_step()==SQLITE_ROW)循环结束后判断
4. 技术细节
  1. 游标初始化

    • 执行 sqlite3_prepare_v2() 后,游标位于结果集起始位置之前
    • 首次 sqlite3_step() 将游标移动到第一行(如果有数据)
  2. 数据访问时机

    // 游标状态变化示例:
    // 初始状态:游标在结果集起始位置之前
    sqlite3_step(stmt);  // 游标移动到第1行,返回SQLITE_ROW
    sqlite3_column_int(stmt,0);  // 读取第1行数据
    sqlite3_step(stmt);  // 游标移动到第2行(或返回SQLITE_DONE)
    
  3. 边界情况

    • 空结果集:首次 sqlite3_step() 直接返回 SQLITE_DONE
    • 最后一行:最后一次返回 SQLITE_ROW 后,下次调用返回 SQLITE_DONE
5. 正确用法模板
// 准备语句
sqlite3_prepare_v2(db, "SELECT id,name FROM table", -1, &stmt, NULL);// 遍历结果集
while(sqlite3_step(stmt) == SQLITE_ROW) {// 游标有效时读取数据int id = sqlite3_column_int(stmt, 0);const char* name = sqlite3_column_text(stmt, 1);
}// 检查是否正常结束
if(sqlite3_errcode(db) == SQLITE_DONE) {printf("成功遍历所有数据\n");
}
**6. 常见误区解释
  • 误区:“SQLITE_DONE 也可以用来读数据”

    • 事实:当返回 SQLITE_DONE 时,游标已失效,任何 sqlite3_column_* 调用都将返回无意义数据
  • 误区:“每次调用 sqlite3_step() 都会执行一次查询”

    • 事实:查询只执行一次,sqlite3_step() 只是移动游标位置
7. 性能优化提示
  1. 在获取文本数据时,优先使用:

    const unsigned char* text = sqlite3_column_text(stmt, col);
    // 而不是拷贝操作(如strcpy)
    
  2. 对于大量数据处理:

    sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0);
    while(sqlite3_step(stmt) == SQLITE_ROW) {// 批量处理
    }
    sqlite3_exec(db, "COMMIT", 0, 0, 0);
    

关于 sqlite3_step()sqlite3_reset() 的正确使用场景

1. 核心区别
函数作用使用场景是否改变语句状态
sqlite3_step()推进语句执行/移动结果集游标每次需要读取新数据或执行操作时调用会改变内部状态
sqlite3_reset()将语句重置到初始状态需要重复使用预处理语句时调用恢复初始状态

2. 为什么示例中不需要 sqlite3_reset()

在之前的SELECT查询示例中:

while (sqlite3_step(stmt) == SQLITE_ROW) {// 读取数据...
}

不需要 reset 的原因是

  1. 单次查询生命周期

    • preparestep循环 → finalize
    • 语句对象即将被销毁(finalize),无需重置
  2. 游标自动管理

    • sqlite3_step() 返回 SQLITE_DONE 时,游标已自动失效
    • 如果不再使用该语句,直接 finalize 更高效

3. 必须使用 sqlite3_reset() 的场景
场景1:重复执行同一条语句
// 准备可复用的语句
sqlite3_prepare_v2(db, "INSERT INTO logs(msg) VALUES(?)", -1, &stmt, NULL);// 第一次执行
sqlite3_bind_text(stmt, 1, "Error1", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_reset(stmt);  // ⚠️ 必须重置!否则下次绑定会失败// 第二次执行
sqlite3_bind_text(stmt, 1, "Error2", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
场景2:批量参数化查询
sqlite3_prepare_v2(db, "UPDATE users SET score=? WHERE id=?", -1, &stmt, NULL);for (int i = 0; i < 100; i++) {sqlite3_bind_int(stmt, 1, scores[i]);sqlite3_bind_int(stmt, 2, ids[i]);sqlite3_step(stmt);sqlite3_reset(stmt);  // ⚠️ 必须重置才能绑定新参数
}

4. 不需要 reset 的例外情况
场景处理方式
语句只用一次直接 finalize
连续执行非参数化语句直接多次 step(如执行多个独立SQL)

5. 底层原理图解
sqlite3_step()
返回SQLITE_ROW
再次调用step()
返回SQLITE_DONE
sqlite3_reset()
sqlite3_finalize()
Prepared
Executing
HasRow
Done

6. 错误用法示例
// ❌ 错误:未reset导致绑定失败
sqlite3_prepare_v2(db, "INSERT INTO t VALUES(?)", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, 100);
sqlite3_step(stmt);// 直接再次绑定会失败!
sqlite3_bind_int(stmt, 1, 200);  // 错误:SQLITE_MISUSE
sqlite3_step(stmt);

修复方法

// ✅ 正确:重置后复用
sqlite3_reset(stmt);  // 重置语句状态
sqlite3_clear_bindings(stmt);  // 可选:清除旧绑定
sqlite3_bind_int(stmt, 1, 200);
sqlite3_step(stmt);

7. 最佳实践总结
  1. 查询数据(SELECT):

    • 单次查询:无需 reset,直接 finalize
    • 复用查询:reset + 重新绑定参数
  2. 修改数据(INSERT/UPDATE/DELETE):

    • 批量操作:每次执行后必须 reset
    • 单次操作:直接 finalize
  3. 事务处理

    sqlite3_exec(db, "BEGIN", 0, 0, 0);
    for (...) {sqlite3_reset(stmt);// 绑定参数...sqlite3_step(stmt);
    }
    sqlite3_exec(db, "COMMIT", 0, 0, 0);
    

8. 性能影响
  • 不必要地调用 reset:增加约 5-10% 的性能开销
  • 漏掉必要的 reset:导致内存泄漏或绑定失败

最终结论

  • 需要 reset:当你想复用预处理语句时(特别是带参数的语句)
  • 不需要 reset:当语句即将被销毁finalize)或只执行一次

记忆口诀

一用到底就销毁(finalize),
想再复用需重置(reset)!

1: 分别封装四个函数:完成增删改查操作<要使用预编译完成>:
  • 增加:根据User结构体,增加一条数据
  • 修改:根据User结构体,修改一条数据
  • 删除:根据id删除一条数据
  • 查询:
    • 根据id查询相应的数据,得到一个User结构体数据
    • 查询所有数据,得到一个User结构体数组
http://www.xdnf.cn/news/917659.html

相关文章:

  • 量子计算突破:新型超导芯片重构计算范式
  • Axure应用交互设计:注册登录页完整交互设计
  • Web前端基础
  • Axure应用交互设计:如何构建注册登录页
  • AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
  • 1.5 Node.js 的 HTTP
  • 9.进程间通信
  • 提供MD5解密的网站
  • JAVA学习 DAY3 注释与编码规范讲解
  • Supersonic 新一代AI数据分析平台
  • 【题解-洛谷】B3622 枚举子集(递归实现指数型枚举)
  • 设计一个算法:删除非空单链表L中结点值为x的第一个结点的前驱结点
  • 零基础玩转物联网-串口转以太网模块如何快速实现与TCP服务器通信
  • 【20250607接单】Spark + Scala + IntelliJ 项目的开发环境配置从零教学
  • Spark 之 AQE
  • OneNet + openssl + MTLL
  • 科学选购儿童用品 | 了解增塑剂(尤其邻苯类)化学成分的来源与用途,为孩子多加一层健康防护。
  • 基于SpringBoot解决RabbitMQ消息丢失问题
  • Srping Cloud Gateway 跨域配置 CorsWebFilter
  • conda指定包安装的channel
  • Java编程之原型模式
  • 线性代数小述(二之前)
  • 什么是预训练?深入解读大模型AI的“高考集训”
  • 【Java学习笔记】SringBuffer类(重点)
  • 集运维_安装linux,麒麟等系统_步骤
  • 64、js 中require和import有何区别?
  • Docker镜像无法拉取问题解决办法
  • natapp 内网穿透失败
  • n8n + AI Agent:AI 自动化生成测试用例并支持导出 Excel
  • 基于 TAPD 进行项目管理