标签(空格分隔): iOS数据库 数据库总结 SQLite数据库
ios中的数据存储方式及其特点
- Preference(偏好设置\NSUserDefaults):也不能存储自定义对象
- NSCoding(NSKeyedArchiver\NSkeyedUnarchiver):归档,局限:一次性存取,读全部读出来,写会覆盖
- SQLite3 :关系型数据库,不能直接存储对象,要将对象拆开存储
- Core Data :对象型的数据库,内部透明
一、什么是SQLite
什么是数据库?
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
数据库可以分为2大种类:1. 关系型数据库(主流) 2. 对象型数据库
SQLite是一款轻型的嵌入式数据库,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了,它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快。
二、SQL语句
在程序运行过程中操作数据库中的数据,得先学会使用SQL语句
1. 什么是SQL
SQL(structured query language):结构化查询语言
SQL是一种对关系型数据库中的数据进行定义和操作的语言
SQL语言简洁,语法简单,好学好用
2. 什么是SQL语句
使用SQL语言编写出来的句子\代码,就是SQL语句
在程序运行过程中,要想操作(增删改查,CRUD)数据库中的数据,必须使用SQL语句
3. SQL语句的特点
不区分大小写(比如数据库认为user和UsEr是一样的)
每条语句都必须以分号 ; 结尾
4. SQL中的常用关键字
select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index等等
5. SQL语句的种类
- 数据定义语句(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)用得最多的操作
其他DQL常用的关键字有where,order by,group by和having
三、数据库操作
表操作
1. 创建表
格式:
create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
create table if not exists 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
示例:
create table t_student (id integer, name text, age inetger, score real) ;
字段类型
integer : 整型值
real : 浮点值
text : 文本字符串
blob : 二进制数据(比如文件)
实际上是无类型的
就算声明为integer类型,还是能存储字符串文本(主键除外)
建表时声明啥类型或者不声明类型都可以,也就意味着创表语句可以这么写:
create table t_student(name, age);
为了保持良好的编程规范、方便程序员之间的交流,编写建表语句的时候最好加上每个字段的具体类型
2. 删除表
格式:
drop table 表名 ;
drop table if exists 表名 ;
示例:
drop table t_student ;
数据操作
1. 插入数据
格式:
insert into 表名 (字段1, 字段2, …) values (字段1的值, 字段2的值, …) ;
示例
insert into t_student (name, age) values (‘mj’, 10) ;
//注意!
数据库中的字符串内容应该用一对单引号 ' 包含
2. 更新数据
格式:
update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, … ;
示例
update t_student set name = ‘jack’, age = 20 ;
//注意!
上面的示例会将t_student表中所有记录的name都改为jack,age都改为20
3. 删除数据
格式:
delete from 表名 ;
示例
delete from t_student ;
注意
上面的示例会将t_student表中所有记录都删掉
4. 条件语句
如果只想删除数据库中国某些指定的记录,那么就必须在DML语句后加上一些条件
条件语句的格式:
where 字段 = 某个值 ; // 不能用两个 =
where 字段 is 某个值 ; // is 相当于 =
where 字段 != 某个值 ;
where 字段 is not 某个值 ; // is not 相当于 !=
where 字段 > 某个值 ;
where 字段1 = 某个值 and 字段2 > 某个值 ; // and相当于C语言中的 &&
where 字段1 = 某个值 or 字段2 = 某个值 ; // or 相当于C语言中的 ||
示例:
将t_student表中年龄大于10 并且 姓名不等于jack的记录,年龄都改为 5
update t_student set age = 5 where age > 10 and name != ‘jack’ ;
删除t_student表中年龄小于等于10 或者 年龄大于30的记录
delete from t_student where age <= 10 or age > 30 ;
猜猜下面语句的作用
update t_student set score = age where name = ‘jack’ ;
将t_student表中名字等于jack的记录,score字段的值 都改为 age字段的值
5. 查询数据
格式:
select 字段1, 字段2, … from 表名 ;
select * from 表名; // 查询所有的字段
示例:
select name, age from t_student ;
select * from t_student ;
select * from t_student where age > 10 ; // 条件查询
6. 起别名(了解)
格式:(字段和表都可以起别名)
select 字段1 别名 , 字段2 别名 , … from 表名 别名 ;
select 字段1 别名, 字段2 as 别名, … from 表名 as 别名 ;
select 别名.字段1, 别名.字段2, … from 表名 别名 ;
示例
select name myname, age myage from t_student ;
给name起个叫做myname的别名,给age起个叫做myage的别名
select s.name, s.age from t_student s ;
给t_student表起个别名叫做s,利用s来引用表中的字段
7. 计算记录的数量
格式:
select count (字段) from 表名 ;
select count ( * ) from 表名 ;
示例
select count (age) from t_student ;
select count ( * ) from t_student where score >= 60;
8. 排序
按照某个字段的值进行排序搜索
select * from t_student order by 字段 ;
Example: select * from t_student order by age ;
//默认是按照升序排序(由小到大),也可以变为降序(由大到小)
//指定升降序
select * from t_student order by age desc ; //降序
select * from t_student order by age asc ; // 升序(默认)
//也可以用多个字段进行排序
//如下,先按照年龄排序(升序),年龄相等就按照身高排序(降序)
select * from t_student order by age asc, height desc ;
9. 精确控制查询结果
使用limit可以精确地控制查询结果的数量,比如每次只查询10条数据
格式:
select * from 表名 limit 数值1, 数值2 ;
示例:
select * from t_student limit 4, 8 ;
//可以理解为:跳过最前面4条语句,然后取8条记录
limit常用来做分页查询
比如每页固定显示5条数据,那么应该这样取数据
第1页:limit 0, 5
第2页:limit 5, 5
第3页:limit 10, 5
…
第n页:limit 5*(n-1), 5
//注:!省略了limit的第一个参数
select * from t_student limit 7 ;
相当于select * from t_student limit 0, 7 ;
表示取最前面的7条记录
约束
1. 简单约束
建表时可以给特定的字段设置一些约束条件,常见的约束有
not null :规定字段的值不能为null
unique :规定字段的值必须唯一
default :指定字段的默认值
//建议:尽量给字段设定严格的约束,以保证数据的规范性
示例
create table t_student (id integer, name text not null unique, age integer not null default 1) ;
//name字段不能为null,并且唯一
//age字段不能为null,并且默认为1
2. 主键约束
如果t_student表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据
良好的数据库编程规范应该要保证每条记录的唯一性,为此,增加了主键约束。也就是说,每张表都必须有一个主键,用来标识记录的唯一性
1. 什么是主键
主键(Primary Key,简称PK)用来唯一地标识某一条记录
例如:t_student可以增加一个id字段作为主键,相当于人的身份证
主键可以是一个字段或多个字段
2. 主键的特点
主键应当是对用户没有意义的
永远也不要更新主键
主键不应包含动态变化的数据
主键应当由计算机自动生成
4. 主键的声明
在创表的时候用primary key声明一个主键
create table t_student (id integer primary key, name text, age integer) ;
integer类型的id作为t_student表的主键
主键字段
只要声明为primary key,就说明是一个主键字段
主键字段默认就包含了not null 和 unique 两个约束
如果想要让主键自动增长(必须是integer类型),应该增加autoincrement
create table t_student (id integer primary key autoincrement, name text, age integer) ;
3. 外键约束
利用外键约束可以用来建立表与表之间的联系
外键的一般情况是:一张表的某个字段,引用着另一张表的主键字段
1. 新建一个外键
create table t_student (id integer primary key autoincrement, name text, age integer, class_id integer, constraint fk_t_student_class_id_t_class_id foreign key (class_id) references t_class (id);
t_student表中有一个叫做fk_t_student_class_id_t_class_id的外键
这个外键的作用是用t_student表中的class_id字段引用t_class表的id字段
2. 表连接查询
什么是表连接查询?
需要联合多张表才能查到想要的数据
表连接的类型
内连接:inner join 或者 join (显示的是左右表都有完整字段值的记录)
左外连接:left outer join (保证左表数据的完整性)
示例
查询0316iOS班的所有学生
select s.name,s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;
三、iOS基于C语言的数据库操作
1.首先创建并打开数据库
// 打开数据库
- (void)openDatabase:(NSString *)SQLiteName {
//1. 获取数据库存储路径
NSString *dbName = [SQLiteName documentDir];
//2. 打开数据库
// 如果数据库不存在,怎新建并打开一个数据库,否则直接打开
if (sqlite3_open(dbName.UTF8String, &_db) != SQLITE_OK) {
NSLog(@"创建/打开数据库失败。");
}
//3. 创建表
if ([self createTable]) {
NSLog(@"创建表成功");
} else {
NSLog(@"创建表失败");
}
}
/**
* 创建数据表
*/
- (BOOL)createTable {
NSString *sql = @"CREATE TABLE IF NOT EXISTS t_person (id integer PRIMARY KEY AUTOINCREMENT,name text,age integer)";
return [self execSql:sql];
}
2. 增删改
sqlite3_exec() 方法可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。我们这里封装一个方法。
/**
* 执行除查询以外的sql语句
*/
- (BOOL)execSql:(NSString *)sql {
if (sqlite3_exec(_db, sql.UTF8String, nil, nil, nil) != SQLITE_OK) {
return NO;
}
return YES;
}
3.查询
因为查询语句需要返回查询结果并提供给UI显示,所以是最麻烦的
查询用到的主要有以下三个方法:
sqlite3_prepare_v2() : 准备数据库,并检查SQL语句是否合法
sqlite3_step() :一条一条获取数据,直到没有记录
sqlite3_coloum_xxx() : 获取对应类型的内容,从0开始。根据实际查询字段的属性,使用sqlite3_column_字段类型 取得对应的内容即可。
查询完要释放资源:
sqlite3_finalize() : 释放stmt
*
* 返回指定sql查询语句运行的结果集
*
* @param sql sql
*
* @return 结果集
*/
- (NSArray *)execRecordSql:(NSString *)sql {
// 1. 评估准备SQL语法是否正确
sqlite3_stmt *stmt = NULL;
/**
* 准备: 理解为预编译SQL语句, 检测里面是否有错误等等, 它可以提供性能
*
* @param db 已经开打的数据库对象
* @param cSQL 需要执行的SQL语句
* @param -1 需要执行的SQL语句的长度, 传入-1系统自动计算
* @param stmt 句柄,靠这个获取查询结果,当成缓冲区就好了
* @param NULL 一般传NULL
*
* @return
*/
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL) != SQLITE_OK) {
NSLog(@"准备数据失败");
}
NSMutableArray *records = [NSMutableArray array];
// 2.查询数据
// sqlite3_step代表取出一条数据, 如果取到了数据就会返回SQLITE_ROW
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 3. 获取/显示查询结果
// sqlite3_column_xxx方法的第二个参数与sql语句中的字段顺寻一一对应(从0开始)
// 获取一条记录的数据
NSDictionary *dict = [self recordWithStmt:stmt];
[records addObject:dict];
}
// 4. 释放句柄
sqlite3_finalize(stmt);
// 返回查询到的数据
return records;
}
/**
获取一条记录的值
- parameter stmt: 预编译好的SQL语句
- returns: 字典
*/
- (NSDictionary *)recordWithStmt:(sqlite3_stmt *)stmt {
//1.拿到当前这条数据的所有列
int count = sqlite3_column_count(stmt);
// 定义字典存储查询到的数据
NSMutableDictionary *record = [[NSMutableDictionary alloc] init];
for (int index = 0; index < count; index++) {
// 2. 拿到每一列的名称
NSString *name = [NSString stringWithCString:sqlite3_column_name(stmt, index) encoding:NSUTF8StringEncoding];
NSLog(@"%@",name);
// 3.拿到每一列的类型 SQLITE_INTEGER
int type = sqlite3_column_type(stmt, index);
switch (type) {
case SQLITE_INTEGER://整形
[record setObject:@(sqlite3_column_int64(stmt, index)) forKey:name];
break;
case SQLITE_FLOAT:
[record setObject:@(sqlite3_column_double(stmt, index)) forKey:name];
break;
case SQLITE3_TEXT:
// 文本类型
[record setObject:[NSString stringWithUTF8String:sqlite3_column_text(stmt, index)] forKey:name];
break;
case SQLITE_NULL:
// 空类型
[record setObject:[[NSNull alloc]init] forKey:name];
break;
default:
// 二进制类型 SQLITE_BLOB
// 一般情况下, 不会往数据库中存储二进制数据
break;
}
}
return record;
四、FMDB框架的使用
这一部分来自:张兴业的CSDN
1.简介
FMDB是iOS平台的SQLite数据库框架,它以OC的方式封装了SQLite的Cell语言API。类似的封装库还有PlausibleDatabase、sqlitepersistentobjects等。FMDB更加简单易用并且支持线程安全所以更加流行。
FMDB是一个开源的项目:GitHub连接
2.添加FMDB框架
从GitHub下载完成后解压,导入fmdb文件夹下的11个文件,如下图:
使用FMDB也必须加入 libsqlite3.dylib 依赖包。
FMDB同时兼容ARC和非ARC工程,会自动根据工程配置来调整相关的内存管理代码。
FMDB常用类:
FMDatabase : 一个单一的SQLite数据库,用于执行SQL语句。
FMResultSet :执行查询一个FMDatabase结果集,这个和Android的Cursor类似。
FMDatabaseQueue :在多个线程来执行查询和更新时会使用这个类。
3.创建数据库
通过指定SQLite数据库文件路径来创建FMDatabase对象
FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
NSLog(@"数据库打开失败!");
}
1、当数据库文件不存在时,fmdb会自己创建一个。
2、 如果你传入的参数是空串:@"",则fmdb会在临时文件目录下创建这个数据库,数据库断开连接时,数据库文件被删除。
3、如果你传入的参数是 NULL,则它会建立一个在内存中的数据库,数据库断开连接时,数据库文件被删除
4.打开和关闭数据库
[db open] ;
[db close];
5.增删改
除了查询操作,FMDB数据库操作都执行executeUpdate方法,这个方法返回BOOL型。
创建表
if ([db open]) {
NSString *sqlCreateTable = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' ('%@' INTEGER PRIMARY KEY AUTOINCREMENT, '%@' TEXT, '%@' INTEGER, '%@' TEXT)",TABLENAME,ID,NAME,AGE,ADDRESS];
BOOL res = [db executeUpdate:sqlCreateTable];
if (!res) {
NSLog(@"error when creating db table");
} else {
NSLog(@"success to creating db table");
}
[db close];
}
添加数据
if ([db open]) {
NSString *insertSql1= [NSString stringWithFormat:
@"INSERT INTO '%@' ('%@', '%@', '%@') VALUES ('%@', '%@', '%@')",
TABLENAME, NAME, AGE, ADDRESS, @"张三", @"13", @"济南"];
BOOL res = [db executeUpdate:insertSql1];
NSString *insertSql2 = [NSString stringWithFormat:
@"INSERT INTO '%@' ('%@', '%@', '%@') VALUES ('%@', '%@', '%@')",
TABLENAME, NAME, AGE, ADDRESS, @"李四", @"12", @"济南"];
BOOL res2 = [db executeUpdate:insertSql2];
if (!res) {
NSLog(@"error when insert db table");
} else {
NSLog(@"success to insert db table");
}
[db close];
}
修改数据
if ([db open]) {
NSString *updateSql = [NSString stringWithFormat:
@"UPDATE '%@' SET '%@' = '%@' WHERE '%@' = '%@'",
TABLENAME, AGE, @"15" ,AGE, @"13"];
BOOL res = [db executeUpdate:updateSql];
if (!res) {
NSLog(@"error when update db table");
} else {
NSLog(@"success to update db table");
}
[db close];
}
删除数据
if ([db open]) {
NSString *deleteSql = [NSString stringWithFormat:
@"delete from %@ where %@ = '%@'",
TABLENAME, NAME, @"张三"];
BOOL res = [db executeUpdate:deleteSql];
if (!res) {
NSLog(@"error when delete db table");
} else {
NSLog(@"success to delete db table");
}
[db close];
}
6.查询
查询操作使用了executeQuery,并涉及到FMResultSet。
if ([db open]) {
NSString * sql = [NSString stringWithFormat:
@"SELECT * FROM %@",TABLENAME];
FMResultSet * rs = [db executeQuery:sql];
while ([rs next]) {
int Id = [rs intForColumn:ID];
NSString * name = [rs stringForColumn:NAME];
NSString * age = [rs stringForColumn:AGE];
NSString * address = [rs stringForColumn:ADDRESS];
NSLog(@"id = %d, name = %@, age = %@ address = %@", Id, name, age, address);
}
[db close];
}
FMDB的FMResultSet提供了多个方法来获取不同类型的数据:
//FMResultSet has many methods to retrieve data in an
//appropriate format:
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnName:
objectForColumnName:
7.多线程操作数据库
如果应用中使用了多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。 应用中不可在多个线程中共同使用一个FMDatabase对象操作数据库,这样会引起数据库数据混乱。 为了多线程操作数据库安全,FMDB使用了FMDatabaseQueue,使用FMDatabaseQueue很简单,首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。 在闭包中操作数据库,而不直接参与FMDatabase的管理。
FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath:database_path];
dispatch_queue_t q1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t q2 = dispatch_queue_create("queue2", NULL);
dispatch_async(q1, ^{
for (int i = 0; i < 50; ++i) {
[queue inDatabase:^(FMDatabase *db2) {
NSString *insertSql1= [NSString stringWithFormat:
@"INSERT INTO '%@' ('%@', '%@', '%@') VALUES (?, ?, ?)",
TABLENAME, NAME, AGE, ADDRESS];
NSString * name = [NSString stringWithFormat:@"jack %d", i];
NSString * age = [NSString stringWithFormat:@"%d", 10+i];
BOOL res = [db2 executeUpdate:insertSql1, name, age,@"济南"];
if (!res) {
NSLog(@"error to inster data: %@", name);
} else {
NSLog(@"succ to inster data: %@", name);
}
}];
}
});
dispatch_async(q2, ^{
for (int i = 0; i < 50; ++i) {
[queue inDatabase:^(FMDatabase *db2) {
NSString *insertSql2= [NSString stringWithFormat:
@"INSERT INTO '%@' ('%@', '%@', '%@') VALUES (?, ?, ?)",
TABLENAME, NAME, AGE, ADDRESS];
NSString * name = [NSString stringWithFormat:@"lilei %d", i];
NSString * age = [NSString stringWithFormat:@"%d", 10+i];
BOOL res = [db2 executeUpdate:insertSql2, name, age,@"北京"];
if (!res) {
NSLog(@"error to inster data: %@", name);
} else {
NSLog(@"succ to inster data: %@", name);
}
}];
}
});
如有需要了解swift FMDB的使用或者更深入了解这个框架请上 GitHub连接
好了数据库就到这里,基本够用了。
码字不易,点个喜欢吧。