数据库编程 SQLITE3 Linux环境

时间:2024-10-28 16:27:17

永久存储程序数据有两种方式:

  1. 用文件存储
  2. 用数据库存储

对于多条记录的存储而言,采用文件时,插入、删除、查找的效率都会很差,为了提高这些操作的效率,有计算机科学家设计出了数据库存储方式


一、数据库

数据库的基本组成:

用来管理数据库的软件被称数据库管理系统

一个数据库管理系统管理着多个数据库

一个数据库中可以包含很多张

一张表中拥有很多记录(行)

一条记录拥有很多字段(列)

每条记录的每个字段存放不同类型的数据

因此,数据库本质上是表的集合


数据库按数据的组织形式分为:

  1. 关系型数据库 ----- 本章内容
  2. 非关系型数据库

数据库管理系统软件按是否支持远程分为:

  1. 网络版 ---- MySql Oracle SQlServer
  2. 单机版 ---- sqlite

网络版的数据管理系统的整体框架:

数据库管理系统发展初期,各家开发此类软件的公司采用的通信协议是不同的,这给应用客户端开发造成很大的麻烦

因此,经过一段时间的发展,全球几家著名的数据库管理系统开发公司制定了统一的通信协议,这个统一的通信协议被称为SQL(Structure Query Language)

随着关系型数据库的不断发展,SQL成为所有数据库管理系统的统一协议

每个数据库管理系统都会提供一个命令行界面的简易客户端,应用程序员可以使用这个简易客户端学习、练习SQL语句以及辅助开发

每一条SQL语句就是一个操作请求


二、SQLite

一个单机版的数据库管理系统

所有操作请求也遵循SQL标准

开源、精悍

SQLite安装:

1.安装SQLite3
命令行下输入:sudo apt-get install sqlite3

2.安装SQLite3编译需要的工具包
命令行下输入: sudo apt-get install libsqlite3-dev

如出现问题尝试:
sudo dpkg --purge --force-depends libsqlite3-0
sudo apt-get install libsqlite3-0
sudo apt-get install -f
sudo apt-get install libsqlite3-dev
sudo apt-get install sqlite3

支持三种字段数据类型:

INTEGER 或 INT 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。
REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。
TEXT 值是一个文本字符串,单引号或双引号括起的字符串

使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储


三、sqlite3简易客户端的使用

1. 运行

Linux>sqlite3 ------ 只是进入sqlite3的命令行界面,不创建打开任何数据库

Linux>sqlite3 路径/数据库文件名 ----- 先创建打开指定的数据库,然后进入sqlite3的命令行界面

sqlite3管理的一个数据库,在Linux系统下用一个文件表示它,习惯上将这个文件命名为 xxx.db

2. 两种命令

  1.  . 开头的命令:简易客户端的内部命令
  2.  以 结尾的命令:前的内容为SQL语句,是简易客户端要求的结尾符

3. 常用内部命令

创建打开一个指定的数据库
sqlite> .open  路径/xxx.db 
sqlite> .help

查看正在使用的数据库
sqlite> .databases

SQL语句操作结果按列对齐  ----- 重要
sqlite> .mode column 

显示某表时是否显示表头 ----- 重要
sqlite> .headers on/off

显示数据中的表 ------ 重要
sqlite> .tables

退出sqlite命令行 ---- 重要
sqlite> .quit


四、常用SQL语句

SQL语句组成:关键字 + 运算符 + 字段名或表名 + 常量

SQL语句的关键字可以全大写、也全小写、甚至可以大小混合,一般建议采用全大写

字段名或表名的大小是区分的,aaa、Aaa、AAA认为是不同的字段名或表名,自建表建议字段名或表名全小写

1. 建表
CREATE TABLE [IF NOT EXISTS] table_name(
   column_name1 datatype[(size)]  [PRIMARY KEY] [AUTOINCREMENT] [NOT NULL],
   column_name2 datatype,
   column_name3 datatype,
   .....
   column_nameN datatype,
);
[]:表示可选,选用时不带[]
[AUTOINCREMENT]:只能用于INTEGER 表示如果插入新记录时没有指定该字段的值,则其值为前一条记录同字段值+1
[PRIMARY KEY]:主键,唯一标识表中的记录  主键:表中每条记录的该字段值不会重复,也即该字段值可以唯一标识一条记录
[IF NOT EXISTS]:如果表不存在则建表,如果存在则什么都不做
[NOT NULL]:表示字段内容不能为空


示例:
sqlite>  CREATE TABLE student(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
   ...>  name TEXT,sex TEXT(1),score REAL); 


2. 删表
DROP TABLE 表名;

示例:
sqlite> CREATE TABLE ttt(xxx TEXT,yyy TEXT);
sqlite> DROP TABLE ttt;

3. 插入新记录
通用:
INSERT INTO table_name(column1, column2,...columnN)  VALUES(value1, value2,...valueN);

不缺字段时:
INSERT INTO table_name  VALUES(value1,value2,...valueN); 


4. 查询记录
SELECT column1, column2, columnN FROM table_name [WHERE condition];
SELECT * FROM table_name [WHERE condition];
*表示查询结果显示所有字段
无WHERE子句则查询所有记录

5. where子句
所谓子句就是不能成为独立的SQL语句,只能成为一条SQL语句的一部分

WHERE子句用来为一些操作指定条件
WHERE condition

condition可以是单个条件或由AND、OR连接的多个条件

比较运算符支持:
=   !=   >   <    >=    <=  用法:字段名 运算符 值

IN()用法:字段名 IN(值1,值2,...,值n)   表示字段值为值1或值2或....

NOT IN()用法:字段名 NOT IN(值1,值2,...,值n)  表示字段值不为值1也不为值2也不为....

LIKE用法:字段名 LIKE 模式字串    模式字串中可以用%号表示任意多个字符,用_表示单个字符,默认不区分大小写

GLOB用法:字段名 GLOB 模式字串    模式字串中可以用*号表示任意多个字符,用?表示单个字符,区分大小写

IS NOT NULL用法:字段名 IS NOT NULL  判断字段值是否为空

IS NULL用法:字段名 IS NULL  判断字段值是否为空

BETWEEN AND用法:字段名 BETWEEN 值1 AND 值2   字段值>=值1 且 字段值<=值2

NOT反条件用法:字段名 NOT  其它条件

6. 修改记录
UPDATE table_name SET column1 = value1, column2 = value2...., columnN = valueN WHERE [condition];
无WHERE子句则对所有记录进行修改


7. 删除记录
DELETE FROM table_name WHERE [condition];
无WHERE子句则删除所有记录


8. Sort子句
ORDER BY 字段名 ASC 或 DESC
ASC升序
DESC降序


9. SQL语句中的函数
datetime('now','localtime') ---- 产生当前日期时间的字符串

使用示例:
INSERT INTO Table_name VALUES(datetime('now','localtime'));

count(*) 对查询结果求记录数
使用示例:
SELECT count(*) FROM student;


五、sqlite3函数库

SQL按执行完毕后是否有记录结果分为两种:

  1. 一种SQL语句的执行只论是否成功。例如:建表、删表、插入记录、修改记录、删除记录
  1. 另一种SQL语句执行完毕产生多条记录结果。例如:查询

sqlite3_open参照图:

sqlite3_exec及其回调函数的参考图:

sqlite3_get_table函数参考图1:

//头文件包含
#include <sqlite3.h>

/*链接选项
	-lsqlite3
*/

//1、打开/创建一个数据库
int sqlite3_open(const char *filename,  sqlite3 **ppDb );
/*
功能:打开指定的数据库,如果数据库并存在则创建并打开指定的数据库,并创建一个后续操作该数据库的引擎
参数:
	filename:带路径的数据库文件名
	ppdb:指向sqlite句柄的指针,调用前定义一个sqlite3 *指针变量,取该指针变量的地址
返回:成功  SQLITE_OK (值为0),否则返回其他值。
*/

//2、如果打开数据库失败,open函数只是返回错误码,如果想要错误原因的字符串描述则调用该函数
const  char  *sqlite3_errmsg(sqlite3 *db);
/*  
	返回值:返回错误信息
*/

//3、回调函数执行sql语句
//更加适用于sql语句执行没有记录结果的情况(此时第3、4参数填NULL)或对需要依次对每条操作结果记录进行处理的情况
int sqlite3_exec(sqlite3* pDB, const char *sql, sqlite_callback callback, void*para, char** errMsg);
 /*
功能:执行SQL语句,查询的结果返回给回调函数callback,多个结果的操作,每个结果都会调用一次callback
参数:
	pDB:数据库引擎的句柄。
	sql:待执行的SQL 语句字符串,以’\0’结尾,不以';'结尾。
	callback:回调函数,用来处理查询结果,如果不需要回调(比如做insert 或者delete 操作时),可以置NULL。
	para:要传入回调函数的指针参数,没有可以置为NULL。
	errMsg:一级指向空间存放着一个char *类型的元素,该元素指向空间(二级指向空间)存放着一个字符串,该字符串内容描述出错原因,
            因此:1. 调用前定义一个char *类型的指针变量,传该指针变量的地址给该形参。
	             2. 由于其二级指向空间是动态分配的,因此使用完后需要调用sqlite3_free函数进行释放;
返回值:执行成功返回SQLITE_OK,否则返回其他值
*/

//4、回调函数
typedef int (*sqlite_callback)(void* para,  int columnCount,  char** columnValue,char** columnName);
/*
功能:由用户处理查询的结果,每找到一条记录自动执行一次回调函数
参数:
	para:从sqlite3_exec()传入的参数指针;
	columnCount:查询到的这一条记录有多少个字段(即这条记录有多少列);
	columnValue:查询出来的数据都保存在这里,它实际上是个1 维数组(不要以为是2 维数组),每一个元素都是一个char * 值,是一个字段内容(用字符串来表示,以‘\0’结尾)
	columnName:与columnValue 是对应的,表示这个字段的字段名称。
返回值:执行成功返回SQLITE_OK,否则返回其他值
*/

//5、关闭
int sqlite3_close(sqlite3 *ppDb);
/*
功能:关闭sqlite数据库。
参数:ppdb:数据库句柄。
返回值:成功返回0,失败返回错误码
*/

//6、释放   
void sqlite3_free(void * errMsg );
/*
功能:释放存放错误信息的内存空间
参数:errMsg: 返回错误信息
*/

//7、非回调来执行sql语句
//更加适用于SQL语句执行后有一条或多条记录结果,且程序期望对所有记录结果的整体进行处理的情况
int sqlite3_get_table(
      sqlite3 *db,          /* An open database */
      const char *zSql,     /* SQL to be evaluated */
      char ***pazResult,    /* Results of the query */
      int *pnRow,           /* Number of result rows written here */
      int *pnColumn,        /* Number of result columns written here */
      char **pzErrmsg       /* Error msg written here */
    );
 /*
功能:非回调来执行sql语句
参数:
	db:数据库句柄
	zSql:要执行的SQL语句
	pazResult:查询结果,一维数组,定义一个char **的二级指针变量,调用函数时传该二级指针变量的地址,不使用应立即调用
	           sqlite3_free_table(result)进行释放
	pnRow:查询出多少条记录(查出多少行) 不包括字段名所在行
	pnColumn:多少个字段(查出多少列)
	pzErrmsg:错误信息
返回值:执行成功返回SQLITE_OK,否则返回其他值
注:pazResult的字段值是连续的,是一维数组不是二维数组,从第0索引到第 pnColumn- 1索引都是字段名称,
                                                 从第 pnColumn索引开始,后面都是字段值。
*/

//8、释放pazResult查询结果   
void sqlite3_free_table(char **pazResult);
/*
	参数:pazResult:指向空间存放着查询结果,指向空间为元素类型是char *的一维数组
*/

示例代码:

#include <stdio.h>
#include <sqlite3.h>

int handle_a_record(void *para,int cols,char **ppval,char **ppname);

int main(){
    sqlite3 *pdb = NULL;
    int ret = 0;
    ret = sqlite3_open("./test.db",&pdb); // open
    if(ret != SQLITE_OK){
        printf("sqlite3 not open,because %s\n",sqlite3_errmsg(pdb));
        sqlite3_close(pdb);
        pdb = NULL;
        return 1;
    }

    {
        char sql[80] = "";
        char *perr = NULL;
        sprintf(sql,"SELECT * FROM stu"); // write instruction
        ret = sqlite3_exec(pdb,sql,handle_a_record,NULL,&perr); // exec()
        if(ret != SQLITE_OK){ // error
            printf("exec %s failed,because %s\n",sql,perr);

            sqlite3_free(perr); // sqlite3_free()
            perr = NULL;
        }
    }

    {
        char sql[80] = "";
        char *perr = NULL;
        char **ppret = NULL;
        int rows = 0;int cols = 0;int i = 0;int j = 0;

        sprintf(sql,"SELECT * FROM stu"); // write instruction
        ret = sqlite3_get_table(pdb,sql,&ppret,&rows,&cols,&perr); // exec()
        if(ret != SQLITE_OK){ // error
            printf("exec %s failed,because %s\n",sql,perr);

            sqlite3_free(perr); // sqlite3_free()
           perr = NULL;
        }
        else{
            for(j = 0;j < cols;j++){
                printf("%-20s ",*(ppret + j));
            }
            printf("\n");
        }
        for(i = 1;i <= rows;i++){
            for(j = 0;j < cols;j++)     printf("%-20s ",*(ppret + i * cols + j));
            printf("\n");
        }
        sqlite3_free_table(ppret);
        ppret = NULL;
}
        sqlite3_close(pdb); // sqlite3_close()
        pdb = NULL;

        return 0;

}
 
int handle_a_record(void *para,int cols,char **ppval,char **ppname){
    int i = 0;
    for(i = 0;i < cols;i++){
        printf("%s | ",*(ppval + i));
    }
    printf("\n");

    return SQLITE_OK;
}

输出:


六、sqlite3函数库的并发模式

  1. 单线程模式 ----- 无锁模式
  2. 串行模式 ------ 默认模式,非阻塞互斥锁实现
  3. 读者写者模式 ------ 读写锁实现

配置sqlite3函数库的并发模式可以通过:1. 重新编译sqlite3函数库的源码 2.调用相关的接口函数进行配置

实际项目中大部分情况采用其默认模式


串行模式时,需要对sqlite3_exec、sqlite3_get_table函数进行如下形式的二次封装:

int my_sqlite3_exec(sqlite3* pdb, const char *sql, sqlite_callback callback, void*para, char** errmsg){
	int ret = 0;  
	do {
		ret = sqlite3_exec( pdb , sql, callback , para , errmsg );  
    	if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED){
			usleep(30 * 1000);
        	continue;
    	}
    	else{
        	break;
    	}
	} while(1);  
    return ret;
}

int my_sqlite3_get_table(
      sqlite3 *db,          /* An open database */
      const char *zSql,     /* SQL to be evaluated */
      char ***pazResult,    /* Results of the query */
      int *pnRow,           /* Number of result rows written here */
      int *pnColumn,        /* Number of result columns written here */
      char **pzErrmsg       /* Error msg written here */
    ){
	int ret = 0;
	do {
		ret = sqlite3_get_table(db,zSql,pazResult,pnRow,pnColumn,pzErrmsg);  
    	if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED){
			usleep(30 * 1000);
        	continue;
    	}
    	else{
        	break;
    	}
	} while(1);  
    return ret;
}

七、二次封装

实际项目代码中,直接调用sqlite3函数库中的函数实现相关功能,会很不方便,往往会将项目中可能用到的所有数据库操作代码单独作为一个模块,该模块又按表分为几个子模块(一般命名为xxxhelper),由这个模块及其子模块向其它项目代码提供需要的数据库操作接口。

public子模块:不隶属于任何一个helper子模块的操作函数,归于本子模块

一般项目中建议采用如下方案:

/*一、public子模块 -------- 项目名_db_public.c*/
/*1. 函数功能:创建打开数据库,然后带IF NOT EXISTS关键字去建表*/
sqlite3 *create_db_engine(const char *pdbfile)
{
	1. 调用sqlite3_open

	2. 建表1
	3. 建表2
	。。。。
	n+1. 建表n

	成功返回引擎地址,失败返回NULL
}

/*2. 对sqllite3_exec的二次封装 ------ 见上一节*/

/*3. 对sqllite3_get_table的二次封装 ------ 见上一节*/

/*4. destroy_db_engine*/
void destroy_db_engine(sqlite3 *pdb)
{
	sqlite3_close(pdb);
}


/*二、对表1的各种操作函数封装  表1名helper.c*/
int insert_new_record_into_表名(sqlite3 *pdb,.........)
{
	组织sql语句
	调用my_sqlite3_exec 或 my_sqlite3_get_table函数执行sql语句
	。。。。。。
}

......

???? xxxxx(sqlite3 *pdb,.......)
{
	通过多次执行sql语句组合完成本函数功能
}


/*三、对表2的各种操作函数封装  表2名helper.c*/

。。。。。。。。。。。。。。。