Sqlite3详细解读



"代码下载:SQLite3_2013_0402详细版.zip" http://vdisk.weibo.com/s/Gb9Qi


***数据库***

严格地说,数据库是按照数据结构来组织、存储和管理数据的仓库。在经济管理的日常工作中,常常需要把某些相关的数据放进这样的仓库,并根据管理的需要进行相应的处理。例如,企业或事业单位的人事部门常常要把本单位职工的基本情况(职工号、姓名、年龄、性别、籍贯、工资、简历等)存放在表中,这张表就可以看成是一个数据库。有了这个"数据仓库"我们就可以根据需要随时查询某职工的基本情况,也可以查询工资在某个范围内的职工人数等等。


这种数据集合具有如下特点:尽可能不重复,以最优方式为某个特定组织的多种应用服务,其数据结构独立于使用它的应用程序,对数据的增、删、改和检索由统一软件进行管理和控制。从发展的历史看,数据库是数据管理的高级阶段,它是由文件管理系统发展起来的。



***结构语言SQL (Structured Query Language) ***


一种对关系数据库中的数据进行定义和操作的句法,为大多数关系数据库管理系统所支持的工业标准。

结构化查询语言是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统可以使用相同的结构化查询语言语言作为数据输入与管理的接口。结构化查询语言语句可以嵌套,这使他具有极大的灵活性和强大的功能。


结构化查询语言包含6个部分:(需掌握一、二、五)


一:数据查询语言(DQL):其语句,也称为数据检索语句,用以从表中获得数据,确定数据怎样在应用程序给出。保留字SELECTDQL(也是所有SQL)用得最多的动词,其他DQL常用的保留字有WHEREORDER BYGROUP BYHANG。这些DQL保留字常与其他类型的SQL语句一起使用。


二:数据操作语言(DML:其语句包括动词INSERTUPDATEDELETE。它们分别用于添加,修改和删除表中的行。也称为动作查询语言。


三:事务处理语言(TPL):它的语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句包括BEGIN TRANSACTIONCOMMITROLLBACK


四:数据控制语言(DCL):它的语句通过GRANTREVOKE获得许可,确定单个用户和用户组对数据库对象的访问。某些RDBMS可用GRANTREVOKE控制对表单个列的访问。


五:数据定义语言(DDL):其语句包括动词CREATEDROP。在数据库中创建新表或删除表(CREAT TABLE DROP TABLE);为表加入索引等。DDL包括许多与人数据库目录中获得数据有关的保留字。它也是动作查询的一部分。


六:指针控制语言(CCL):它的语句,像DECLARE CURSORFETCH INTOUPDATE WHERE CURRENT用于对一个或多个表单独行的操作。



数据类型

结构化查询语言中有五种数据类型:字符型,文本型,数值型,逻辑型和日期型。


1. 字符型  VARCHAR VS CHAR

VARCHAR型和CHAR型数据的这个差别是细微的,但是非常重要。他们都是用来储存字符串长度小于255的字符。VARCHAR型字段的另一个突出的好处是它可以比CHAR型字段占用更少的内存和硬盘空间。当你的数据库很大时,这种内存和磁盘空间的节省会变得非常重要。使用VARCHAR型字段时,你不需要为剪掉你数据中多余的空格而操心。


2. 文本型   TEXT

使用文本型数据,你可以存放超过二十亿个字符的字符串。当你需要存储大串的字符时,应该使用文本型数据。

注意文本型数据没有长度,而上一节中所讲的字符型数据是有长度的。一个文本型字段中的数据通常要么为空,要么很大。

无论何时,只要你能避免使用文本型字段,你就应该不适用它。文本型字段既大且慢,滥用文本型字段会使服务器速度变慢。文本型字段还会吃掉大量的磁盘空间。一旦你向文本型字段中输入了任何数据(甚至是空值),就会有2K的空间被自动分配给该数据。除非删除该记录,否则你无法收回这部分存储空间。


3. 数值型整数INT 、小数NUMERIC、钱数MONEY

一个INT型数据占用四个字节。这看起来似乎差别不大,但是在比较大的表中,字节数的增长是很快的。另一方面,一旦你已经创建了一个字段,要修改它是很困难的。因此,为安全起见,你应该预测以下,一个字段所需要存储的数值最大有可能是多大,然后选择适当的数据类型。


4. 逻辑型   BIT

如果你使用复选框(CHECKBOX)从网页中搜集信息,你可以把此信息存储在BIT型字段中。BIT型字段只能取两个值:01

当心,在你创建好一个表之后,你不能向表中添加 BIT型字段。如果你打算在一个表中包含BIT型字段,你必须在创建表时完成。


5. 日期型  DATETIME VS SMALLDATETIME

一个 DATETIME型的字段可以存储的日期范围是从175311日第一毫秒到99991231日最后一毫秒。DATETIME型字段在你输入日期和时间之前并不包含实际的数据,认识这一点是重要的。



SQL使用方式

简单的结构化查询语言查询只包括SELECT选择列表、FROM子句和WHERE子句。它们分别说明所查询列、查询的表或视图、以及搜索条件等。


一、选择列表  选择列表(select_list)指出所查询列,它可以是一组列名列表、星号、表达式、变量(包括局部变量和全局变量)等构成。


1、选择所有列

例如,下面语句显示testtable表中所有列的数据:

SELECT *FROM testtable


2、选择部分列并指定它们的显示次序

查询结果集合中数据的排列顺序与选择列表中所指定的列名排列顺序相同。


3、更改列标题

在选择列表中,可重新指定列标题。定义格式为:

列标题=列名 列名列标题

如果指定的列标题不是标准的标识符格式时,应使用引号定界符,例如,下列语句使用汉字显示列标题:

SELECT 昵称=nickname,电子邮件=emailFROM testtable


4、删除重复行

SELECT语句中使用ALLDISTINCT选项来显示表中符合条件的所有行或删除其中重复的数据行,默认为ALL。使用DISTINCT选项时,对于所有重复的数据行在SELECT返回的结果集合中只保留一行。


5、限制返回的行数

使用TOP n [PERCENT]选项限制返回的数据行数,TOP n说明返回n行,而TOP n PERCENT时,说明n是表示一百分数,指定返回的行数等于总行数的百分之几。


二、FROM子句

FROM子句指定SELECT语句查询及与查询相关的表或视图。在FROM子句中最多可指定256个表或视图,它们之间用逗号分隔。

FROM子句同时指定多个表或视图时,如果选择列表中存在同名列,这时应使用对象名限定这些列所属的表或视图。


三、WHERE子句

WHERE子句设置查询条件,过滤掉不需要的数据行。

WHERE子句可包括各种条件运算符:

比较运算符(大小比较):>;、>==<;、<=<>;、!>;、!<

范围运算符(表达式值是否在指定的范围):BETWEEN…AND…

NOT BETWEEN…AND…

列表运算符(判断表达式是否为列表中的指定项):IN (项1,项2……

NOT IN (项1,项2……

模式匹配符(判断值是否与指定的字符通配格式相符):LIKENOT LIKE

空值判断符(判断表达式是否为空):IS NULLIS NOT NULL

逻辑运算符(用于多条件的逻辑连接):NOTANDOR

1、范围运算符例:age BETWEEN 10 AND 30相当于age>=10 AND age<=30

2、列表运算符例:country IN ('Germany','China')

3、模式匹配符例:常用于模糊查找,它判断列值是否与指定的字符串格式相匹配。可用于charvarchartextntextdatetimesmalldatetime等类型查询。

可使用以下通配字符:

百分号%:可匹配任意类型和长度的字符,如果是中文,请使用两个百分号即%%

下划线_:匹配单个任意字符,它常用来限制表达式的字符长度。

方括号[]:指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个。[^]:其取值也[] 相同,但它要求所匹配对象为指定字符以外的任一个字符。


四、查询结果排序

使用ORDER BY子句对查询返回的结果按一列或多列排序。ORDER BY子句的语法格式为:

ORDER BY {column_name [ASC|DESC]} […n]

其中ASC表示升序,为默认值,DESC为降序。ORDER BY不能按ntexttextimage数据类型进行排序。



*** SQL语句的添加、删除、修改***


数据记录筛选注意:单双引号的用法可能有误(没有测式)  

Sql = "Select Distinct 字段名 From 数据表"  

   Distinct函数,查询数据库存表内不重复的记录

Sql = "Select Count(*) From 数据表 where 字段名1>#18:0:0# and 字段名1< #19:00# "  

   count函数,查询数库表内有多少条记录,字段名1”是指同一字段

例:

set rs=conn.execute("select count(id) as idnum from news")  

response.write rs("idnum")  

sql="select * from 数据表 where 字段名 between 1 and 2"  

Sql="select * from 数据表 where 字段名 between #2003-8-10# and #2003-8-12#"  

在日期类数值为2003-8-10 19:55:08 的字段里查找2003-8-102003-8-12的所有记录,而不管是几点几分。

select * from tb_name where datetime between #2003-8-10# and #2003-8-12#  

字段里面的数据格式为:2003-8-10 19:55:08,通过sql查出2003-8-102003-8-12的所有纪录,而不管是几点几分。

Sql="select * from 数据表 where 字段名=字段值 order by 字段名 [desc]"  

Sql="select * from 数据表 where 字段名 like '%字段值%' order by 字段名 [desc]"  


模糊查询

Sql="select top 10 * from 数据表 where 字段名 order by 字段名 [desc]"

查找数据库中前10记录

Sql="select top n * form 数据表 order by newid()"  

随机取出数据库中的若干条记录的方法  top nn就是要取出的记录数

Sql="select * from 数据表 where 字段名 in ('1','2','3')"


添加数据记录

sql="insert into 数据表 (字段1,字段2,字段3 „) valuess (1,2,3 „)"  

sql="insert into 数据表 valuess (1,2,3 „)"  

不指定具体字段名表示将按照数据表中字段的顺序,依次添加

sql="insert into 目标数据表 select * from 源数据表"  

把源数据表的记录添加到目标数据表


更新数据记录

Sql="update 数据表 set 字段名=字段值 where 条件表达式"  

Sql="update 数据表 set 字段1=1,字段2=2 „„ 字段n=n where 条件表达式"  

Sql="update 数据表 set 字段1=1,字段2=2 „„ 字段n=n "  

没有条件则更新整个数据表中的指定字段值


删除数据记录

Sql="delete from 数据表 where 条件表达式"  

Sql="delete from 数据表"  没有条件将删除数据表中所有记录)


数据记录统计函数

AVG(字段名) 得出一个表格栏平均值

COUNT(*|字段名) 对数据行数的统计或对某一栏有值的数据行数统计

MAX(字段名) 取得一个表格栏最大的值

MIN(字段名) 取得一个表格栏最小的值

SUM(字段名) 把数据栏的值相加


引用以上函数的方法:

sql="select sum(字段名) as 别名 from 数据表 where 条件表达式"  

set rs=conn.excute(sql)  

rs("别名") 获取统的计值,其它函数运用同上。


数据表的建立和删除

CREATE TABLE 数据表名称(字段1 类型1(长度),字段2 类型2(长度) „„ )  

例:CREATE TABLE tab01(name varchar(50),datetime default now())  

DROP TABLE 数据表名称 (永久性删除一个数据表)


记录集对象的方法

rs.movenext 将记录指针从当前的位置向下移一行

rs.moveprevious 将记录指针从当前的位置向上移一行

rs.movefirst 将记录指针移到数据表第一行

rs.movelast 将记录指针移到数据表最后一行

rs.absoluteposition=N 将记录指针移到数据表第N

rs.absolutepage=N 将记录指针移到第N页的第一行

rs.pagesize=N 设置每页为N条记录

rs.pagecount 根据 pagesize 的设置返回总页数

rs.recordcount 返回记录总数

rs.bof 返回记录指针是否超出数据表首端,true表示是,false为否

rs.eof 返回记录指针是否超出数据表末端,true表示是,false为否

rs.delete 删除当前记录,但记录指针不会向下移动

rs.addnew 添加记录到数据表末端

rs.update 更新数据表记录




***常见关系型数库***

Oracle:大型

MySQL:小型

SQLite,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 TclC#PHPJava等,还有ODBC接口,同样比起MysqlPostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。不像常见的客户端/服务器结构范例,SQLite引擎不是个程序与之通信的独立进程,而是连接到程序中成为它的一个主要部分。所以主要的通信协议是在编程语言内的直接API调用。这在消耗总量、延迟时间和整体简单性上有积极的作用。整个数据库(定义、表、索引和数据本身)都在宿主主机上存储在一个单一的文件中。它的简单的设计是通过在开始一个事务的时候锁定整个数据文件而完成的。


SQLite 支持跨平台,操作简单,能够使用很多语言直接创建数据库,而不象Access一样需要Office的支持。如果你是个很小型的应用,或者你想做嵌入式开发,没有合适的数据库系统,那么现在你可以考虑使用SQLite。同时因为数据库结构简单,系统源代码也不是很多,也适合想研究数据库系统开发的专业人士。



SQLite支持哪些数据类型些?

NULL    值为NULL

INTEGER 值为带符号的整型,根据类别用123468字节存储

REAL    值为浮点型,8字节存储

TEXT    值为text字符串,使用数据库编码(UTF-8, UTF-16BE or UTF-16-LE)存储

BLOB    值为二进制数据,具体看实际输入;比如要在数据库中存放一张图片,这张图片就会以二进制形式存放,在sqlite中对应的数据类型就是BLOB



但实际上,sqlite3也接受如下的数据类型:

smallint  16 位元的整数

interger  32 位元的整数

decimal(p,s)  p 精确值和 s 大小的十进位整数,精确值p是指全部有几个数(digits)大小值s是指小数点後有几位数。如果没有特别指定,则系统会设为 p=5; s=0

float   32位元的实数。

double   64位元的实数。

char(n)   n 长度的字串,n不能超过 254

varchar(n)  长度不固定且其最大长度为 n 的字串,n不能超过 4000

graphic(n)   char(n) 一样,不过其单位是两个字元 double-bytes n不能超过127这个形态是为了支援两个字元长度的字体,例如中文字。

vargraphic(n)  可变长度且其最大长度为 n 的双字元字串,n不能超过 2000

date   包含了年份、月份、日期。

time   包含了小时、分钟、秒。

timestamp  包含了年、月、日、时、分、秒、千分之一秒。



@如果不往数据库里面添加任何的表,这个数据库等于没有建立,不会在硬盘上产生任何文件,如果数据库已经存在,则会打开这个数据库。


@SQL 标准规定,在字符串中,单引号需要使用逃逸字符,即在一行中使用两个单引号。


@INTEGER PRIMARY KEY属性,有什么特性?

如果将声明表的一列设置为 INTEGER PRIMARY KEY,则具有:

1.每当你在该列上插入一NULL值时, NULL自动被转换为一个比该列中最大值大1的一个整数;

2.如果表是空的,将会是1

注意该整数会比表中该列上的插入之前的最大值大1该键值在当前的表中是唯一的。但有可能与已从表中删除的值重叠。要想建立在整个表的生命周期中唯一的键值,需要在 INTEGER PRIMARY KEY 上增加AUTOINCREMENT声明。那么,新的键值将会比该表中曾能存在过的最大值大1



/* 对于SQlite3,所有的API函数都有一个前缀:sqlite3_。这个前缀表明这些APIsSQlite数据库产品提供,3代表版本。所有的常量都有一个前缀:SQLITE_SQlite数据库的源码是完全开放的,对于提供给客户应用程序调用的API,函数名全部由小写字符组成。如sqlite3_create_function_v2就是一个PUBLIC API,而sqlite3CreateFunc就是一个SQlite内部函数。

对于使用SQlite数据库的客户来说,提供一套稳定的API非常重要,否则SQlite每出来一个Release版本,之前的API就焕然一新,那客户应用程序就需要修改自己的APP,付出很大的维护代价。所以SQlite数据库的API,一旦发布就不会删除或者修改,如果某个API确实有必要改进,就会提供一个加了“v2”后缀的新API函数,而保留以前的旧版本,这样客户APP无需修改依然可以正常运行。比如:sqlite3_create_functionsqlite3_create_function_v2

  SQliteC APIs包括一定数量的数据结构,接近二百个函数,还有两三百个常量。虽然API的数量比较多,但用起来并不复杂,其中只有一部分函数是经常使用的,还有很多函数的功能相似。

该例直接使用SQLite提供的C接口API,而且使用新版的带_v2API.

为求简单没有界面显示数据库内容,需要数据库查看工具辅助。

*/



以下是代码实现:

#import "ViewController.h"
// 首先添加libsqlite3.0.dylib库,再导入头文件
#import <sqlite3.h>
// 数据库名称
#define DatabaseName @"database.sqlite3"
@interface ViewController () {
    sqlite3* _pDB; // 指向SQLite数据库的指针,非OC对象
}
- (const char* )databasePath;
- (IBAction)clickButton:(id)sender;
@end



- (void)viewDidLoad
{
    [super viewDidLoad];
    /* 在执行任何SQL语句之前,必须首先连接到一个数据库,也就是打开或者新建一个SQlite3数据库文件。连接数据库由sqlite3_open函数完成,它一共有上面3个版本。其中 sqlite3_open函数假定SQlite3数据库文件名为UTF-8编码,sqlite3_open_v2是它的加强版。sqlite3_open16函数假定SQlite3数据库文件名为UTF-16(Unicode宽字符)编码。
      */
                                                  
    _pDB = NULL; // 定义指向代表SQLite数据库结构体的指针
    // SQlite3数据库文件的扩展名没有一个标准定义,比较流行的选择是.sqlite3、.db、.db3。不过在Windows系统平台上,不推荐使用.sdb作为 SQlite3数据库文件的扩展名,据说这会导致IO速度显著减慢,因为.sdb扩展名有其特殊用义。
    // 调用SQlite API时,如果成功则会返回SQLITE_OK,如果调用失败将返回一个错误码(Error code),指明发生了什么错误。对API调用的返回值进行适当检查,可以提高程序的健壮性。
    NSInteger status = sqlite3_open_v2([self databasePath], &_pDB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
    /* sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs)
     参数说明:
     filename:需要被打开的数据库文件的文件名,在sqlite3_open和sqlite3_open_v2中这个参数采用UTF-8编码,而在sqlite3_open16中则采用UTF-16编码。注意该参数是c字符串而不是OC.
                                                   
     ppDb:参数ppDb看起来有点复杂,它是一个指向指针的指针。当调用sqlite3_open_xxx函数时,该函数将分配一个新的SQlite3数据结构,然后初始化,然后将指针ppDb指向它。所以客户应用程序可以通过sqlite3_open_xxx函数连接到名为filename的数据库,并通过参数ppDb返回指向该数据库数据结构的指针。如果sqlite不能分配内存来存放sqlite对象,ppDb将会被返回一个NULL值。
                                                   
     flags:作为数据库连接的额外控制的参数,可以是SQLITE_OPEN_READONLY,SQLITE_OPEN_READWRITE和SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE中的一个,用于控制数据库的打开方式。
     SQLITE_OPEN_READONLY,则SQlite3数据库文件以只读的方式打开,如果该数据库文件不存在,则sqlite3_open_v2函数执行失败,返回一个error。
     SQLITE_OPEN_READWRITE,则SQlite3数据库文件以可读可写的方式打开,如果该数据库文件本身被操作系统设置为写保护状态,则以只读的方式打开。如果该数据库文件不存在,则sqlite3_open_v2函数执行失败,返回一个error。
     SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,则SQlite3数据库文件以可读可写的方式打开,如果该数据库文件不存在则新建一个。这也是sqlite3_open和sqlite3_open16函数的默认行为。除此之外,flags还可以设置为其他标志,具体可以查看SQlite官方文档。
                                                   
     zVfs:允许客户应用程序命名一个虚拟文件系统(Virtual File System)模块,用来与数据库连接。VFS作为SQlite library和底层存储系统(如某个文件系统)之间的一个抽象层,通常客户应用程序可以简单的给该参数传递一个NULL指针,以使用默认的VFS模块。
    */
    if (SQLITE_OK != status) {
        NSLog(@"数据库打开失败,%@", NSStringFromSelector(_cmd));
    }
                                                  
    sqlite3_close(_pDB); // 关闭数据库
    _pDB = NULL;
    //在使用完SQlite数据库之后,需要调用sqlite3_close函数关闭数据库连接,释放数据结构所关联的内存,删除所有的临时数据项。如果在调用sqlite3_close函数关闭数据库之前,还有某些没有完成的(nonfinalized)SQL语句,那么sqlite3_close函数将会返回SQLITE_BUSY错误。客户程序员需要finalize所有的预处理语句(prepared statement)之后再次调用sqlite3_close。
}




// 用于返回沙盒下Document的完整路径(C字符串)
- (const char* )databasePath
{
    NSString* documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString* fullPath = [documentPath stringByAppendingPathComponent:DatabaseName];
    return [fullPath cStringUsingEncoding:NSUTF8StringEncoding];
}



- (IBAction)clickButton:(id)sender {
    UIButton* button = (UIButton* )sender;
                                         
    // 建议:执行任何操作前,打开数据库,操作完成后关闭
    NSInteger status = 0;
    if (_pDB == NULL) {
        status = sqlite3_open_v2([self databasePath], &_pDB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
        if (SQLITE_OK != status) {
            NSLog(@"数据库打开失败,%@", NSStringFromSelector(_cmd));
            sqlite3_close(_pDB);
            _pDB = NULL;
        }
    }
                                         
    switch (button.tag) {
        case 100: { // 创建表
            [self createTableWithDatabase:_pDB];
        } break;
                                                 
        case 200: { // 添加一行数据
            [self addDataWithDatabase:_pDB];
        } break;
                                                 
        case 300: {
            [self deleteFromDatabase:_pDB];
        } break;
                                                 
        case 400: {
            [self selectWithDatabase:_pDB];
        } break;
                                                 
        default:
            break;
    }
                                         
    // 操作完成后关闭数据库
    sqlite3_close(_pDB);
    _pDB = NULL;   
}






// 这里创建一个表
- (void)createTableWithDatabase:(sqlite3* )db {
                              
    if (!db) {
        return;
    }
    // 定义一个sqlite3_stmt结构体的指针,用于保存编译成字节码的SQL语句
    // 在sqlite中并没有定义sqlite3_stmt这个结构的具体内容,它只是一个抽象类型,在使用过程中一般以它的指针进行操作
    sqlite3_stmt* stmt = NULL;
                              
    // 需要执行的SQL语句,其中的保留字大小写无关,如“create”和“CREATE”一样
    char* szSql = "CREATE TABLE UserInfo(id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, money REAL)";
                              
    NSInteger status = sqlite3_prepare_v2(db, szSql, -1, &stmt, NULL);
    /*
           sqlite3_prepare_v2(sqlite3 *db,
                       const char *szSql,
                       int nByte,
                       sqlite3_stmt **ppStmt,
                       const char **pzTail)
            这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针。这个接口需要一个数据库连接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行(evaluate)这个SQL语句,它仅仅为执行准备这个sql语句(编译SQL语句为字节码留给后面的执行函数执行)。
             参数:
             db:数据指针
                               
             szSql:sql语句,使用UTF-8编码
                               
             nByte:szSql的字节长度。如果nByte为负值,则prepare函数会自动计算出szSql的字节长度,不过要确保szSql传入的是以NULL结尾的字符串。如果nByte不是负的,那么它就是这个函数能从szSql中读取的字节数的最大值。如果nBytes为负,szSql在第一次遇见’/000/或’u000’的时候终止。如果SQL命令字符串中只包含一条SQL语句,那么它没有必要以“;”结尾。
                               
             ppStmt:一个指向指针的指针,用来传回一个指向新建的sqlite3_stmt结构体的指针,sqlite3_stmt结构体里面保存有转换好的SQL语句。如果SQL命令字符串包含多条SQL语句,同时参数pzTail不为NULL,那么它将指向SQL命令字符串中的下一条SQL语句。如果错误发生,它被置为NULL。调用过程必须负责在编译好的sql语句完成使用后使用sqlite3_finalize()删除它。
                               
             pzTail:上面提到szSql在遇见终止符或者是达到设定的nByte之后结束,假如szSql还有剩余的内容,那么这些剩余的内容被存放到pZTail中,不包括终止符
                                       
             说明
             如果执行成功,则返回SQLITE_OK,否则返回一个错误码。推荐在现在任何的程序中都使用sqlite3_prepare_v2这个函数,sqlite3_prepare只是用于前向兼容。
             prepared语句可以被重置(调用sqlite3_reset函数),然后可以重新绑定参数之后重新执行。sqlite3_prepare_v2函数代价昂贵,所以通常尽可能的重用prepared语句。最后,这条prepared语句确实不在使用时,调用sqlite3_finalize函数释放所有的内部资源和sqlite3_stmt数据结构,有效删除prepared语句。
        */
    if (status == SQLITE_OK) { // 如果该表存在,会返回 SQLITE_ERROR
                                  
        // CREATE TABLE语句没有返回值,调用sqlite3_step函数执行这条语句
        // 通过调用sqlite3_step一次或多次来执行前面sqlite3_prepare创建的准备语句。这个语句执行到结果的第一行可用的位置,如需继续前进到结果的第二行的话,只需再次调用sqlite3_setp()
        // 对于不返回结果的语句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只执行一次就返回
        NSInteger result = sqlite3_step(stmt);
        /*
                说明:sqlite3_exec 这个方法可以执行那些没有返回结果的操作,例如创建、插入、删除等。
                它属于早期遗留下来的便捷函数,这些函数存在很多缺点。当然他们依然存在就有理由——使用方便。它们的优点也仅仅是使用方便,而不是具有很好的性能。对于这些便捷函数,它们并没有什么特别之处,只是在这些函数内部调用sqlite3_prepare_xxx、sqlite3_step、sqlite3_finalize等API函数来完成一站式功能。在这样的函数内部往往存在很多额外的类型转换,所以这些函数很可能会比我们自己去调用sqlite3_prepare_xxx、sqlite3_step、sqlite3_finalize等API执行的更慢一些。
               */
        if (result != SQLITE_DONE) {
            NSLog(@"创建表失败,%@", NSStringFromSelector(_cmd));
        }
    } else {
        NSLog(@"该表已经存在或创建失败");
    }
                              
    sqlite3_finalize(stmt); // 摧毁stmt结构体,释放资源
}








// 插入数据
- (void)addDataWithDatabase:(sqlite3* )db {
    if (!db) {
        return;
    }
                     
    // 准备工作,解释见 createTableWithDatabase
    sqlite3_stmt* stmt = NULL;
    char* szSql = "INSERT INTO UserInfo(date, money) VALUES(?, ?)";
    /* SQL插入语句
        插入语句有几种形式,标准的为:"insert into 数据表 (字段1,字段2,字段3, ...) valuess (值1,值2,值3, ...)"
        其中valuess (值1,值2,值3, ...) 中的值可以使用具体的数值,也可以使用如下的占位的通配符:
        ?
        ?NNN,NNN代表数字
        :VVV,VVV代表字符
        @VVV
        $VVV
        这里只介绍 ?,其它的自己查SQL文档。相同的通配符在同一个SQL声明中出现多次, 在这种情况下所有相同的通配符都会被替换成相应的值. 没有被绑定的通配符将自动取NULL值。使用sqlite3_bind_*()来给这些参数绑定值,用sqlite3_clear_bindings重设这些绑定。
        SQL语句字符串可以带?号,它是SQL语句中的不确定部分,需要对它另外赋值。事实上,SQLite的官方文档中已经明确指出,在很多时候sqlite3_prepare_v2函数的执行时间要多于sqlite3_step函数的执行时间,因此建议使用者要尽量避免重复调用sqlite3_prepare_v2函数。在我们的实现中,如果想避免此类开销,只需将待插入的数据以变量的形式绑定到SQL语句中,这样该SQL语句仅需调用sqlite3_prepare_v2函数编译一次即可,其后的操作只是替换不同的变量数值。
        sqlite3_reset() 函数可以用来重置一个SQL声明的状态(sqlite3_stmt),使得它回到未执行前的状态,并不改变绑定值。
       */
                     
    NSInteger status = sqlite3_prepare_v2(db, szSql, -1, &stmt, NULL);
    if (SQLITE_OK != status) {
        NSLog(@"插入数据失败,%@", NSStringFromSelector(_cmd));
        sqlite3_finalize(stmt);
        return;
    }
                     
    /* 绑定参数
         int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
         int sqlite3_bind_double(sqlite3_stmt*, int, double);
         int sqlite3_bind_int(sqlite3_stmt*, int, int);
         int sqlite3_bind_int64(sqlite3_stmt*, int, long long int);
         int sqlite3_bind_null(sqlite3_stmt*, int);
         int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
         int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
         int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
         以上是 sqlite3_bind 所包含的全部接口,它们是用来给SQL声明中的通配符赋值的. 没有绑定的通配符则被认为是空值。在准备SQL语句过程中,绑定是可选的。其中的第二个参数表示该绑定参数对应在SQL语句中?的索引值。第三个参数为替换的具体值。
        */
    NSDate* now = [NSDate date]; // 获得当前GMT时间
//    NSLog(@"date = %@", now);
    // 第一个绑定参数
    sqlite3_bind_text(stmt, 1, [[now description] UTF8String], -1, SQLITE_STATIC);
    /* 上面第四个参数为字符串的长度,该值为负将返回第一遇到'\0'的位置。
           第五个参数为一个函数指针,SQLITE3执行完操作后回调此函数,通常用于释放字符串占用的内存。此参数有两个常数,SQLITE_STATIC告诉sqlite3_bind_text函数字符串为常量,可以放心使用;而SQLITE_TRANSIENT会使得sqlite3_bind_text函数对字符串做一份拷贝。
       */
    // 第二个绑定参数
    double money = arc4random() % 1000 * 0.01; // 随机数值
    sqlite3_bind_double(stmt, 2, money);
                     
    // 如是,stmt完全准备好了,下面就是执行工作,我们依然使用sqlite3_step
    // 对于不返回结果的语句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只执行一次就返回
    // 返回SQLITE_BUSY表示暂时无法执行操作,SQLITE_DONE表示操作执行完毕,SQLITE_ROW表示执行完毕并且有返回(执行select语句时)。当返回值为SQLITE_ROW时,我们需要对查询结果进行处理,SQLITE3提供sqlite3_column_*系列函数。
    NSInteger result = sqlite3_step(stmt);
    if (SQLITE_DONE == result) {
        NSLog(@"插入一条数据");
    } else {
        NSLog(@"插入数据失败,%@", NSStringFromSelector(_cmd));
    }
    sqlite3_finalize(stmt); // 摧毁stmt结构体,释放资源
}





// 删除最后一条数据
- (void)deleteFromDatabase:(sqlite3* )db {
    // 删除一般伴随查找,建议先看查找
    // 首先找到最后一条数据的主键(id)
    // SQLite中语法的不同,不能使用top 1,应使用LIMIT 0,1表示从第0条记录开始,往后读取1条记录
    char* selectSql = "SELECT * FROM UserInfo ORDER BY id DESC LIMIT 0,1";
    sqlite3_stmt* stmt = NULL;
    NSInteger Status = sqlite3_prepare_v2(db, selectSql, -1, &stmt, NULL);
               
    if (SQLITE_OK != Status) {
        sqlite3_finalize(stmt);
        return;
    }
               
    NSInteger nIndex = -1;
    while (SQLITE_ROW == sqlite3_step(stmt)) {
        nIndex = sqlite3_column_int(stmt, 0);
        NSLog(@"最后一行的id = %i", nIndex);
    }
               
    sqlite3_finalize(stmt); // 摧毁stmt结构体,释放资源
    if (nIndex > -1) {
        sqlite3_stmt* st = NULL;
        // 删除该条记录
        char* deleteSql = "DELETE FROM UserInfo WHERE id = ?";
        NSInteger result = sqlite3_prepare_v2(db, deleteSql, -1, &st, NULL);
                   
        if (SQLITE_OK == result) {
            // 多此一举,使用绑定
            sqlite3_bind_int(st, 1, nIndex);
                       
            while (SQLITE_ROW == sqlite3_step(st)) {
            }
        }
        sqlite3_finalize(st);
    }  
}






// 查询数值
- (void)selectWithDatabase:(sqlite3* )db {
    // SQL的筛选语句有非常多的形式,这里只介绍常见的两种:
    // "select * from 数据表 where 字段名=字段值 order by 字段名 [desc]"
    // "where"后面接筛选条件;"order by"表示排序方式(可选);ACS表示按正序排序(从小到大排序,默认),DESC 表示按倒序排序(从大到小排序)
    // "select * from 数据表 where 字段名 in ('值1','值2','值3')"
        
    // 查找所有"money"字段值大于2的所有数据
    char* szSql = "SELECT * FROM UserInfo WHERE money>2";
    sqlite3_stmt* stmt = NULL;
    sqlite3_prepare_v2(db, szSql, -1, &stmt, NULL);
    // 如果SQL语句中包含?号,需要使用sqlite3_bind_*()来给这些参数绑定值,这里没有。
        
    // 返回SQLITE_BUSY表示暂时无法执行操作,SQLITE_DONE表示操作执行完毕,SQLITE_ROW表示执行完毕并且有返回(执行select语句时)。当返回值为SQLITE_ROW时,我们需要对查询结果进行处理,SQLITE3提供sqlite3_column_*系列函数。
    /*
         sqlite3_column()
         这个过程从执行sqlite3_step()执行一个准备语句得到的结果集的当前行中返回一个列。每次sqlite3_step得到一个结果集的列停下后,这个过程就可以被多次调用去查询这个行的各列的值。对列操作是有多个函数,均以sqlite3_column为前缀
         const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
         int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
         int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
         double sqlite3_column_double(sqlite3_stmt*, int iCol);
         int sqlite3_column_int(sqlite3_stmt*, int iCol);
         sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
         const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
         const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
         int sqlite3_column_type(sqlite3_stmt*, int iCol);
         sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
         说明
         第一个参数为从sqlite3_prepare返回来的prepared statement对象的指针,第二参数指定这一行中的想要被返回的列的索引。最左边的一列的索引号是0,行的列数可以使用sqlite3_colum_count()获得。这些函数会根据情况去转换数值的类型。
         单步执行一次将返回一个结果(一条数据),一条数据可能包含多列(每列对应一个字段)。
        */
    while (SQLITE_ROW == sqlite3_step(stmt)) {
        // 第0列对应字段“id”
        NSInteger nId = sqlite3_column_int(stmt, 0);
            
        // 第1列对应字段“date”
        char* szDate = (char* )sqlite3_column_text(stmt, 1);
        NSString* strDate = [NSString stringWithUTF8String:szDate];
            
        // 第2列对应字段“money”
        double dMoney = sqlite3_column_double(stmt, 2);
        NSLog(@"id = %i, date = %@, money = %g", nId, strDate, dMoney);
    }
        
    sqlite3_finalize(stmt); // 摧毁stmt结构体,释放资源
}