在iOS开发中经常需要对数据进行存储,平常我们进行简单的数据存储会用到plist存储,或者偏好设置的值,用户账户密码等比较单一的数据使用NSUserDefaults ,但是遇到比较复杂的,数量比较大得数据,用前两种方法不是很合适.iOS平台提供了一种轻量级的数据存储方式sqlite数据库进行存储数据.

        那么什么是sqlite?sqlite是一款轻量型的嵌入式数据库,,它占用资源非常少,在嵌入式设备中,可能只需要几百K的内存就够了.它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快.

        我们经常使用的关系型数据库,比如:Oracle、MySQL、SQL Server、Access、DB2、Sybase ,但是在嵌入式/移动客户端有sqlite.

关系型数据库是数据库的一种,数据库主要分两大类,关系型数据库和对象型数据库.

        数据库的存储结构和excel很像,以表为单位.创建一张表之后,然后可以在创建好的表中添加多个字段(column,列,属性),然后就可以添加多行记录了(row,每行存放多个字段对应的值).sqlite语句主要就是四个字,增删改查.并且不区分大小写,每条语句都以分号结尾,不能以关键字命名表明和字段.

        sqlite语句中常用的关键字有:select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index 等等.

        

sqlite语句的种类:

        数据定义语句(DDL:Data Definition Language )

create和drop等操作 

create table或 drop table)

        数据操作语句(DML:Data Manipulation Language )

insert、update、delete等操作 

                上面的3种操作分别用于添加、修改、删除表中的数据

        数据查询语句(DQL:Data Query Language )

                可以用于查询获得表中的数据 

select是DQL(也是所有SQL)用得最多的操作 

where,order by,group by和having


创表:

格式:    

            create table 表名(字段名1 字段类型1,字段名2 字段类型2,...);

            create table if not exists 表名(字段名1 字段类型1,字段名2 字段类型2,...);

举例:     

create table if not exists t_myCode(id interger,name text,age text);

在上述示例中,integer等是数据存储类型

        在sqlite中数据划分为以下几种类型:

        integer,real,text,blob(整型,浮点型,文本字符串,二进制文件)

        但是在sqlite中,实际上是没有数据类型的划分的,即使声明为integer类型,也能存储blob类型的文件或者text类型的字符串,但是主键除外,建表时,实际上是可以不声明数据类型的,但是这不是一个良好的编程习惯,也许以后你自己就会忘了,这个字段你需要存储什么类型的文件,也不方便其他程序员阅读你的代码,所以在建表是最好加上每个字段的类型.

primary key autoincrement设置成主键,这几个英文单词的意思是,设置为主键,并且主键自动递增

举例:

create table if not exists t_myCode (id integer primary key autoincrement,name text,age text);


删表:

格式:

        drop table 表名;

        drop table if exists 表名;

举例:

drop table if exists t_myCode;


插入数据:

格式:

        insert into 表名(字段1,字段2,...) values(字段1的值,字段2的值,...);

举例:

        //字符串内容有单引号括,不要用双引号

insert into t_myCode(name,age) values('nie',22);

更新数据:

格式

        update 表名 set 字段1 = 字段1的值,字段2 = 字段2的值,...;

举例:

        //这里会将表中所有的name都改成yang,age改成30

update t_myCode set name = 'yang',age = 30;

删除数据:

格式:

        delete frome 表名

举例:

        //这里会将t_mycode表中的所有数据都删除

delete frome t_myCode

        接下来是查询语句,查询语句是用的最多的操作,对需要的数据进行条件判断,如果只想更新或者删除某些固定的记录,那就必须在DML语句后加上一些条件.

//条件语句的常见格式

where 字段 = 某个值 ;   // 不能用两个 =

where 字段 is 某个值 ;   // is 相当于 = 

where 字段 != 某个值 ; 

where 字段 is not 某个值 ;   // is not 相当于 != 

where 字段 > 某个值 ; 

where 字段1 = 某个值 and 字段2 > 某个值 ;  // and相当于C语言中的 &&

where 字段1 = 某个值 or 字段2 = 某个值 ;  //  or 相当于C语言中的


        按照myCode表中年龄大于10,并且姓名不等于yang的记录,年龄都改为5

                update t_myCode set age = 5 where age >10 and name !='yang';

        删除t_myCode表中年龄小于等于10或者年龄大于30的记录

                delete from t_myCode where age <=10 or age >30;

        将表中名字等于yang的记录,height字段的值都改为age字段的值

                update t_myCode set height = age where name = 'yang';

DQL查询:

格式:

        select 字段1,字段2,...from 表名;

        select * from 表名;

举例:

        select name,age from t_myCode;

        select * from t_myCode;

        select * from t_myCode where age >10; 

        在这些查询语句中可以给字段和表名分别添加别名,select 字段1 别名1,字段2 别名2,...from 表名 别名;

在字段和别名之间可以加上as,不过可以省略,不写是一样的效果,select 字段 as 别名,...from 表名 as 别名;

举例:

        select name sname,age sage from t_mycode as c;

如果表名有别名,那么可以通过表的别名来引用表中的字段,类似于oc中的点语法

        select c.name,c.age from t_myCode as c;

        当然在sqlite中也有提供一些函数,比如计算某个字段的数量,平均数,等等

        比如:计算数量

        select count(字段) from 表名;

        select count(*) from 表名;

        select count(*) from 表名 where 条件;    

排序:数据库中默认是按照升序排列的 , 要进行排序,在后面添加 order] by 字段 desc

例如:select * from t_mycode order by age desc;

多个字段排序也行,是按照前面的排序,然后再按照后面的字段排序,有先后顺序

例如:select * from t_myCode order by age desc,height asc;

(desc是降序,asc是升序,默认)

分页控制:limit,用于精确查询结果的数量,比如每次只想查询5条数据.

格式:

        select * from 表名 limit 数值1,数值2;

意思是:跳过数值1前面的语句,然后取数值2条记录.

举例:

        select *from t_myCode limit 10,20;

简单约束:

建表时,可以给特定的字段设置一些约束条件,常见的约束有

not null:规定字段的值不能为null

unique:规定字段的值唯一

default:指定字段的默认值

create table t_mycode(id integer primary key autoincrement,name text not null unique,age text not null defoult 1);

name字段不能为null,并且唯一

age字段不能为null,并且默认为1

主键约束:

        就是上面提到的主键就是这个,当表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据.这就需要一个主键约束,主键最好要建立在业务逻辑之外,就是说,业务逻辑中没有主键的字段.

        好的数据库编程规范应该保证每条记录的唯一性,为此,增加了主键约束,也就是说,每张表都必须要有一个主键,作为唯一标示符.

表中的某一个字段只要声明了primary key ,就是一个主键字段了,主键字段默认包含了not null和unique这个两个约束.如果想让主键自动增长应该加入autoincrement,声明上面主键有举例.

外键约束:

        利用外键约束可以建立表与表之间的联系,外检的一半情况是:一张表的某个字段,引用这另一张表中的主键字段

creat table t_class(id integer primary key autoincrement,score text);

create table t_student (id integer primary key autoincrement, name text, age integer,class_id,fk_t_student_class_id_t_class_id(class_id) (id)) ; references t_class 

t_student表中有一个叫做fk_t_student_class_id_t_class_id的外键 

这个外键的作用是用t_student表中的class_id字段引用t_class表的id字段

sqlite3.h头文件

首先声明实例变量

sqlite3 *_db; // db代表着整个数据库,db是数据库实例

在使用前需要打开数据库,打开后才能创表

// 0.获得沙盒中的数据库文件名

NSString *filename = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];


// 1.创建(打开)数据库(如果数据库文件不存在,会自动创建)

int result = sqlite3_open(filename.UTF8String, &_db);

if (result == SQLITE_OK) {

    NSLog(@"成功打开数据库");

    

    // 2.创表

    const char *sql = "create table if not exists t_student (id integer primary key autoincrement, name text, age integer);";

    char *errorMesg = NULL;

    int result = sqlite3_exec(_db, sql, NULL, NULL, &errorMesg);

    if (result == SQLITE_OK) {

        NSLog(@"成功创建t_student表");

    } else {

        NSLog(@"创建t_student表失败:%s", errorMesg);

    }

} else {

    NSLog(@"打开数据库失败");

}

之后可以进行插入更新删除操作

- (void)insert

{

    for (int i = 0; i<30; i++) {

        NSString *name = [NSString stringWithFormat:@"Jack-%d", arc4random()%100];

        int age = arc4random()%100;

        NSString *sql = [NSString stringWithFormat:@"insert into t_student (name, age) values('%@', %d);", name, age];

        

        char *errorMesg = NULL;

        int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errorMesg);

        if (result == SQLITE_OK) {

            NSLog(@"成功添加数据");

        } else {

            NSLog(@"添加数据失败:%s", errorMesg);

        }

    }

}

更新和删除操作和这个插入一样,只是sqlite语言改变一下,但是这里要注意sql这个字符串如果是这样拼接可能会有sql注入的漏洞,应该由占位符来替代

以下是查询语句

- (void)query

{

    // 1.定义sql语句

const char *sql = "select id, name, age from t_student where name = ?;";

    

    // 2.定义一个stmt存放结果集

    sqlite3_stmt *stmt = NULL;

    

    // 3.检测SQL语句的合法性

    int result = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);

    if (result == SQLITE_OK) {

        NSLog(@"查询语句是合法的");

        

        // 设置占位符的内容

        sqlite3_bind_text(stmt, 1, "jack", -1, NULL);

        

        // 4.执行SQL语句,从结果集中取出数据

        while (sqlite3_step(stmt) == SQLITE_ROW) { // 真的查询到一行数据

            // 获得这行对应的数据

            

            // 获得第0列的id

            int sid = sqlite3_column_int(stmt, 0);

            

            // 获得第1列的name

            const unsigned char *sname = sqlite3_column_text(stmt, 1);

            

            // 获得第2列的age

            int sage = sqlite3_column_int(stmt, 2);

            

            NSLog(@"%d %s %d", sid, sname, sage);

        }

    } else {

        NSLog(@"查询语句非合法");

    }

}

FMDB的使用

创表

@property (nonatomic, strong) FMDatabase *db;

// 0.获得沙盒中的数据库文件名

NSString *filename = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];


// 1.创建数据库实例对象

self.db = [FMDatabase databaseWithPath:filename];


// 2.打开数据库

if ( [self.db open] ) {

    NSLog(@"数据库打开成功");

    

    // 创表

    BOOL result = [self.db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer);"];

    

    if (result) {

        NSLog(@"创表成功");

    } else {

        NSLog(@"创表失败");

    }

} else {

    NSLog(@"数据库打开失败");

}

插入

- (void)insert

{

    for (int i = 0; i<40; i++) {

        NSString *name = [NSString stringWithFormat:@"rose-%d", arc4random() % 1000];

        NSNumber *age = @(arc4random() % 100 + 1);

        [self.db executeUpdate:@"insert into t_student (name, age) values (?, ?);", name, age];

    }

}

更新,在fmdb中除了查询方法其它所有方法都看做是更新,调用 executeUpdate方法,只是sqlite语句不一样

"delete from t_student where name = ?;",@"jack",只要下面的sqlite替换成这个就是删除了

- (void)update

{

    [self.db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

}

查询

- (void)query

{

    // 1.查询数据

    FMResultSet *rs = [self.db executeQuery:@"select * from t_student where age > ?;", @50];

    

    // 2.遍历结果集

    while (rs.next) {

        int ID = [rs intForColumn:@"id"];

        NSString *name = [rs stringForColumn:@"name"];

        int age = [rs intForColumn:@"age"];

        

        NSLog(@"%d %@ %d", ID, name, age);

    }


在FMDB中还有队列类,这是线程安全的

创建队列,并建表

@property (nonatomic, strong) FMDatabaseQueue *queue;


// 0.获得沙盒中的数据库文件名

NSString *filename = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];


// 1.创建数据库队列

self.queue = [FMDatabaseQueue databaseQueueWithPath:filename];


// 2.创表

[self.queue inDatabase:^(FMDatabase *db) {

    BOOL result = [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer);"];

    

    if (result) {

        NSLog(@"创表成功");

    } else {

        NSLog(@"创表失败");

    }

}];

插入

- (IBAction)insert

{

    [self.queue inDatabase:^(FMDatabase *db) {

        for (int i = 0; i<40; i++) {

            NSString *name = [NSString stringWithFormat:@"rose-%d", arc4random() % 1000];

            NSNumber *age = @(arc4random() % 100 + 1);

            [db executeUpdate:@"insert into t_student (name, age) values (?, ?);", name, age];

        }

    }];

更新,队列中开启事务可以直接用这个block来自动实现,但如果是basedb可手动开启事务

- (IBAction)update

{

    [self.queue inDatabase:^(FMDatabase *db) {


        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        

//        if (发现情况不对){

//            // 回滚事务

//            [db rollback];

//            [db executeUpdate:@"rollback transaction;"];

//        }

        

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

      

    }];

}

 // 开启事务

        [db executeUpdate:@"begin transaction;"];

        [db beginTransaction];

    

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        

        

        if (发现情况不对){

            // 回滚事务

            [db rollback];

            [db executeUpdate:@"rollback transaction;"];

        }

    

        

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        

        // 提交事务

        [db commit];

        [db executeUpdate:@"commit transaction;"];

删除


- (IBAction)delete

{

    [self.queue inTransaction:^(FMDatabase *db, BOOL *rollback) {

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        [db executeUpdate:@"update t_student set age = ? where name = ?;", @20, @"jack"];

        

//        if (发现情况不对){

//            // 回滚事务

//            *rollback = YES;

//        }

    }];

}

查询,rs指向结果集的最前面.每调用next方法后指向下一个结果,知道下一个结果为空

- (IBAction)query

{

    [self.queue inDatabase:^(FMDatabase *db) {

        // 1.查询数据

        FMResultSet *rs = [db executeQuery:@"select * from t_student where age > ?;", @50];

        

        // 2.遍历结果集

        while (rs.next) {

            int ID = [rs intForColumn:@"id"];

            NSString *name = [rs stringForColumn:@"name"];

            int age = [rs intForColumn:@"age"];

            NSLog(@"%d %@ %d", ID, name, age);

        }

    }];

}

1.打开数据库

int sqlite3_open(
    const char *filename,   // 数据库的文件路径
    sqlite3 **ppDb          // 数据库实例
);


2.执行任何SQL语句

int sqlite3_exec(
    sqlite3*,                                  // 一个打开的数据库实例
    const char *sql,                           // 需要执行的SQL语句
    int (*callback)(void*,int,char**,char**),  // SQL语句执行完毕后的回调
    void *,                                    // 回调函数的第1个参数
    char **errmsg                              // 错误信息
);


3.检查SQL语句的合法性(查询前的准备)

int sqlite3_prepare_v2(
    sqlite3 *db,            // 数据库实例
    const char *zSql,       // 需要检查的SQL语句
    int nByte,              // SQL语句的最大字节长度
    sqlite3_stmt **ppStmt,  // sqlite3_stmt实例,用来获得数据库数据
    const char **pzTail
);


4.查询一行数据

int sqlite3_step(sqlite3_stmt*); // 如果查询到一行数据,就会返回SQLITE_ROW


5.利用stmt获得某一字段的值(字段的下标从0开始)

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 void *sqlite3_column_blob(sqlite3_stmt*, int iCol); // 二进制文本数据
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);  // 字符串数据