在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); // 字符串数据