数据库编程 SQLITE3 Linux环境

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

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

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


一、数据库

数据库的基本组成:

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

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

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

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

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

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

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


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

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

  2. 非关系型数据库


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

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

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

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

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

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

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

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


二、SQLite

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

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

开源、精悍

SQLite安装:

cpp 复制代码
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

支持三种字段数据类型:

INTEGERINT 值是一个带符号的整数,根据值的大小存储在 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. 常用内部命令

cpp 复制代码
创建打开一个指定的数据库
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认为是不同的字段名或表名,自建表建议字段名或表名全小写

cpp 复制代码
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:

cpp 复制代码
//头文件包含
#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 *的一维数组
*/

示例代码:

cpp 复制代码
#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函数进行如下形式的二次封装:

cpp 复制代码
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子模块的操作函数,归于本子模块

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

cpp 复制代码
/*一、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*/

。。。。。。。。。。。。。。。
相关推荐
book01214 分钟前
MySql数据库运维学习笔记
运维·数据库·mysql
纠结哥_Shrek6 分钟前
Oracle和Mysql的区别
数据库·mysql·oracle
极客先躯6 分钟前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分
leoufung8 分钟前
VIM FZF 安裝和使用
linux·编辑器·vim
做梦敲代码8 分钟前
达梦统计信息
数据库·达梦数据库
jiugie1 小时前
MongoDB学习
数据库·python·mongodb
hzulwy1 小时前
MongoDB应用设计调优
数据库·mongodb
bugtraq20211 小时前
XiaoMi Mi5(gemini) 刷入Ubuntu Touch 16.04——安卓手机刷入Linux
linux·运维·ubuntu
我爱松子鱼1 小时前
MySQL 单表访问方法详解
数据库·mysql
我们的五年1 小时前
MySQL存储引擎:选择与应用
数据库·mysql