一、数据库的简单介绍
- 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
- 数据库可以分为2大种类:关系型数据库(主流)和对象型数据库
- 常用关系型数据库
- PC 端:Oracle(收费)、MySQL(国内免费)、SQL Server 、Access、DB2、Sybase
- 嵌入式\移动客户端:SQLite
二、SQLite的介绍
- SQLite是一款轻型的嵌入式数据库
- 它占用的资源非常的低,在嵌入式的设备中,可能只需要几百K的内存就够了
- 它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快
三、SQL和SQL语句的介绍以及SQL语句的特点
- SQL(structured query language):结构化查询语言,它还是一种对关系型数据库中的数据进行定义和操作的语言,而且它的语言简洁,语法简单,好学好用。
- SQL语句:使用SQL语言编写出来的句子\代码,就是SQL语句,在程序运行的过程中,要想操作(增删改查)数据库中的数据,必须使用SQL语句
- SQL语句的特点
- 不区分大小写(比如数据库的person和PerSon是一样的)
- 每条语句必须以分号结尾(;)
- SQL中的关键字有很多
select、insert、update、delete、from、create、where、desc、order等等 - 数据库不可以使用关键字来命名表、字段
- SQLlite数据存储类型:integer(整型值),real(浮点值),text(文本字符串),blob(二进制数据,例如文件)
四、SQL语句的种类(DDL,DML,DQL)
下面的学习会涉及到一个数据库工具Navicat
,请到QQ群:584599353
的文件库常用软件下载,找群主要注册码
- 1、数据定义语句(DDL: Data Definition Language),包括create和drop等操作,在数据库中建表或删除表(create table和drop table)
- 具体的使用:
- 1.1、创建表的语句
- IF NOT EXISTS 判断创建的表是否存在,只有不存在才会创建
- PRIMARY KEY 代表该字典是主键
- AUTOINCERMENT 代表字段自动增长
CREATE TABLE IF NOT EXISTS T_Person2( id INTEGER PRIMARY KEY AUTOINCERMENT, name TEXT, age INTEGER );
- 1.2、删除表( IF EXISTS 判断是否存在,只有存在才会删除)
DROP TABLE IF EXISTS T_Person2;
- 2、数据操作语句(DML: Data Manipulation Language),包括insert、update、delete等操作,上面的三种操作分别用于添加、修改,删除表中的数据
- 2.1、添加(插入数据:insert)(注意:前后两个括号是对应的,数据库中的字符串要加单引号)
INSERT INTO T_person (name,age) VALUES ('wc',26);
- 2.2、更新数据(update)(下面的代码会把数据库中的所有名字和年龄都改了)
UPDATE T_person SET name = 'jk';更改了所有人名字 UPDATE T_person SET name = '王冲' WHERE age = 30; 更改了年龄等于30人的名字
- 2.3、删除数据(下面的是不对的,会删除整个表的)
DELETE FROM T_person; 删除表中所有的数据了 DELETE FROM T_person WHERE age = 28; 删除了表中年龄等于28的人
- 2.4、条件语句的常见格式
WHERE 字段 = 某个值; 不可以用两个= WHERE 字段 is 某个值; is 相当于 = WHERE 字段 != 某个值 WHERE 字段 is not 某个值 is not 相当于 != WHERE 字段 > 某个值; WHERE 字段1 = 某个值 and 字段2 > 某个值 // and相当于oc里面的&& WHERE 字段1 = 某个值 or 字段2 = 某个值 // and相当于oc里面的||
- 3、数据查询语句(DQL: Data Query Langue),可以查询获得表中的数据,关键字select是DQL(也是左右SQL)用的最多的操作,其他DQL常用的关键字有where,order by,group by 和having
- 3.1、查询整个表
SELECT * FROM T_Person2; // T_Person2是表名
- 3.2、查询符合条件的表中数据(年龄大于13的人)
SELECT name,age FROM T_Person2 WHERE age > 13;
- 3.3、给字段起别名
SELECT name as nameJ,age as ageK FROM T_Person2;
- 3.4、给表起别名 (同时查询不同的表,里面包含相同的字段的时候)
SELECT table1.name,table1.age,table2.name,table2.age FROM T_Person2 as table1,T_person as table2;
- 3.5、计算记录的数量
- SELECT count(*或者字段) FROM T_Person2(表名) : 查询表中某一个字段有多少条
SELECT count(*) FROM T_Person2
- 加约束条件的查询 (年龄大于20的条数)
SELECT count(*) FROM T_Person2 WHERE age > 20
- 3.6、查询的结果用
- 单个字段排序(默认是升序
ASC
,降序是DESC
)
SELECT * FROM T_Person2 ORDER BY age DESC
- 多个字段排序 (年龄按照降序排序,遇到年龄相同的,再按降序排序)
SELECT * FROM T_Person2 ORDER BY age DESC , id DESC;
- 3.6、
LIMIT
分页查询
- LIMIT数值1,数值2
- 数值1:代表跳过几条
- 数值2:代表取几条
- 如果只取一个数值代表取前几条
取前3条 SELECT * FROM T_Person2 LIMIT 3; 跳过前3条取后面的4条 SELECT * FROM T_Person2 LIMIT 3,4;
- 如果按照上面的分页,下面可以用一个公式概括
10 代表一页10条数据 n代表第几页 SELECT * FROM T_Person2 LIMIT 10*(n-1),10;
- 3.7、对建表的字段的简单约束(如下面的方式建立一个表)
- NOT NULL : 规定字段的值不能为null
- UNIQUE: 规定字段的值不能一样
- DEFAULT: 指定的字段设置默认值
CREATE TABLE IF NOT EXISTS T_Person5( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER DEFAULT 20 );
五、SQLite在Xcode里面的运用
- 5.1、下面会按照创建数据库、建表、插入数据、更新数据、删除数据、多条数据的插入、多条数据插入时间的优化(事件的添加)、预插入数据和事件的结合。
- 5.2、创建一个单例类
JKSQLiteManger
import UIKit class JKSQLiteManger: NSObject { private static let manger: JKSQLiteManger = JKSQLiteManger() // 单粒 class func shareManger() -> JKSQLiteManger { return manger; } }
- 5.3、创建数据库、建表(在使用之前打开,一般可以放到
AppDelegate
里面),下面的\n
是为了打印换行方便看,+
是为了把上下两句话连成一句话,数据的名字要定义为JK.sqlite
的格式,下面T_Person8
是表名,name
,age
:字段名,id
:主键名
private var db: OpaquePointer? = nil // MARK: 打开数据库 func openDB(SQLiteName: String) { // 0.拿到数据库的路径 let path = String.cacheDir() + "/\(SQLiteName)" print(path) let cPath = path.cString(using: String.Encoding.utf8)! // 1.打开数据库 /** 1.1、cPath: 需要打开的数据库文件的路径,这里的路径是C语言字符串的路径 1.2、打开之后的数据库对象(指针),以后所有的数据库操作,都必须拿到这个指针才能进行相关的操作 */ // open方法的特点:如果指定路径的数据库文件已经存在,就会直接打开,否则就会创建一个新的 if sqlite3_open(cPath, &db) != SQLITE_OK{ print("打开数据库失败") return } // 2.创建表 if creatTable(){ print("创建表成功") }else{ print("创建表失败") } } //MARK: 创建表 func creatTable() -> Bool { // 1、编写SQL语句 /** 建议:在开发中编写SQL语句,如果语句过长,不要写在一行 技巧:在做数据库开发时候,如果遇到错误,可以先将SQL打印出来,拷贝到PC工具中验证之后再进行调试 */ let sql = "\n CREATE TABLE IF NOT EXISTS T_Person8( \n" + "id INTEGER PRIMARY KEY AUTOINCREMENT, \n" + "name TEXT, \n" + "age INTEGER \n" + "); \n" print(sql) // 2、执行SQL语句 // 在SQLite3中,除了查询以外(创建/删除/新增/更新)都使用同一个函数 return execSQL(sql: sql) }
- 5.4、插入、更新、删除 一条数据
/** 插入一条数据*/ let sql = "INSERT INTO T_Person8 (name,age) VALUES ('\(name!)',\(age));" /** 更新一条数据*/ let sql = "UPDATE T_Person8 SET name = '\(name)' WHERE age = \(self.age);" /** 删除一条数据*/ let sql = "DELETE FROM T_Person8 WHERE age = \(self.age);" // MARK:执行除查询以外的SQL语句 /** - Parameter sql: 要执行的SQL语句 - Returns: 是否执行成功 true:执行成功 false:执行失败 */ func execSQL(sql: String) -> Bool { // 1.将Swift的字符串转换为C语言的字符串 let cSQL = sql.cString(using: String.Encoding.utf8)! // 在SQLite3中,除了查询以外(创建/删除/新增/更新)都使用同一个函数 /** 1、已经打开的数据库对象 2、需要执行的SQLite语句,C语言字符串 3、执行SQLite语句后面的回调,一般传nil 4、第三个参数的第一个参数,一般传nil 5、错误信息一般传nil */ if sqlite3_exec(db, cSQL, nil, nil, nil) != SQLITE_OK{ return false } return true }
- 5.5、查询数据
let sql = "SELECT * FROM T_Person8;" let res = JKSQLiteManger.shareManger().execRecordSQL(sql: sql) // MARK:查询所有的数据 func execRecordSQL(sql: String) ->[[String: AnyObject]] { // 0.将Swift字符串转换为C语言字符串 let cSQL = sql.cString(using: String.Encoding.utf8)! // 1.准备数据 // 准备: 理解为预编译SQL语句, 检测里面是否有错误等等, 它可以提供性能 /* 1.已经开打的数据库对象 2.需要执行的SQL语句 3.需要执行的SQL语句的长度, 传入-1系统自动计算 4.预编译之后的句柄, 已经要想取出数据, 就需要这个句柄 5. 一般传nil */ var stmt: OpaquePointer? = nil if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK { print("准备失败") } // 准备成功 var records = [[String: AnyObject]]() // 2.查询数据 // sqlite3_step代表取出一条数据, 如果取到了数据就会返回SQLITE_ROW while sqlite3_step(stmt) == SQLITE_ROW { // 获取一条记录的数据 let record = recordWithStmt(stmt: stmt!) // 将当前获取到的这一条记录添加到数组中 records.append(record) } // 3.关闭STMT // 注意点: 只要用到了stmt, 一定要关闭 sqlite3_finalize(stmt) // 返回查询到的数据 return records } /** 获取一条记录的值 :param: stmt 预编译好的SQL语句 :returns: 字典 */ private func recordWithStmt(stmt: OpaquePointer) ->[String: AnyObject] { // 2.1拿到当前这条数据所有的列 let count = sqlite3_column_count(stmt) // print(count) // 定义字典存储查询到的数据 var record = [String: AnyObject]() for index in 0..<count { // 2.2拿到每一列的名称 let cName = sqlite3_column_name(stmt, index) //let name = String(CString: cName, encoding: NSUTF8StringEncoding)! let name = String(cString: cName!, encoding: String.Encoding.utf8) // print(name) // 2.3拿到每一列的类型 SQLITE_INTEGER let type = sqlite3_column_type(stmt, index) // print("name = \(name) , type = \(type)") switch type { case SQLITE_INTEGER: // 整形 let num = sqlite3_column_int64(stmt, index) record[name!] = Int(num) as AnyObject case SQLITE_FLOAT: // 浮点型 let double = sqlite3_column_double(stmt, index) record[name!] = Double(double) as AnyObject case SQLITE3_TEXT: // 文本类型 let cText = UnsafePointer(sqlite3_column_text(stmt, index)) let text = String.init(cString: cText!) record[name!] = text as AnyObject case SQLITE_NULL: // 空类型 record[name!] = NSNull() default: // 二进制类型 SQLITE_BLOB // 一般情况下, 不会往数据库中存储二进制数据 print("") } } return record }
- 5.6、大批量数据插入的优化问题(下面将使用事务和 异步串行的队列来快速的插入数据)
// MARK: 事务相关 // 1.开启事务 func beginTransaction(){ execSQL(sql: "BEGIN TRANSACTION") } // 2.提交事务 func commitTransaction(){ execSQL(sql: "COMMIT TRANSACTION") } // 3.回滚 func rollbackTransaction(){ execSQL(sql: "ROLLBACK TRANSACTION") } // MARK: 创建一个异步串行队列来执行数据的插入,防止界面卡顿 private let dbQueue = DispatchQueue(label: "com.520it.lnj") func execQueueSQL(action:@escaping (_ manager: JKSQLiteManger)->()) { // 1.开启一个子线程 dbQueue.async { //print(Thread.current) // 2.执行闭包 action(self) } }
- 5.7、预编译优化数据库
// MARK: 预编译优化数据库 func batchExecSQL() { let start = CFAbsoluteTimeGetCurrent() let manager = JKSQLiteManger.shareManger() // 开启事务 manager.beginTransaction() for i in 0..<10000 { let sql = "INSERT INTO T_Person8" + "(name, age)" + "VALUES" + "(?, ?);" manager.batchExecSQL(sql: sql, args: "yy +\(i)", 1 + i) } // 提交事务 manager.commitTransaction() print("耗时 = \(CFAbsoluteTimeGetCurrent() - start)") } // 自定义一个SQLITE_TRANSIENT, 覆盖系统的 private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) // MARK: - 预编译优化数据库 func batchExecSQL(sql:String, args: CVarArg...) -> Bool { // 1.将SQL语句转换为C语言 let cSQL = sql.cString(using: String.Encoding.utf8)! // 2.预编译SQL语句 var stmt: OpaquePointer? = nil if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK { print("预编译失败") sqlite3_finalize(stmt) return false } // 3.绑定数据 var index:Int32 = 1 for objc in args { if objc is Int { //print("通过int方法绑定数据 \(objc)") // 第二个参数就是SQL中('?', ?)的位置, 注意: 从1开始 sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int)) }else if objc is Double { //print("通过Double方法绑定数据 \(objc)") sqlite3_bind_double(stmt, index, objc as! Double) }else if objc is String { // print("通过Text方法绑定数据 \(objc)") let text = objc as! String let cText = text.cString(using: String.Encoding.utf8)! // 第三个参数: 需要绑定的字符串, C语言 // 第四个参数: 第三个参数的长度, 传入-1系统自动计算 // 第五个参数: OC中直接传nil, 但是Swift传入nil会有大问题 /* typedef void (*sqlite3_destructor_type)(void*); #define SQLITE_STATIC ((sqlite3_destructor_type)0) #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) 第五个参数如果传入SQLITE_STATIC/nil, 那么系统不会保存需要绑定的数据, 如果需要绑定的数据提前释放了, 那么系统就随便绑定一个值 第五个参数如果传入SQLITE_TRANSIENT, 那么系统会对需要绑定的值进行一次copy, 直到绑定成功之后再释放 */ sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT) } index = index + 1 } // 4.执行SQL语句 if sqlite3_step(stmt) != SQLITE_DONE { print("执行SQL语句失败") return false } // 5.重置STMT if sqlite3_reset(stmt) != SQLITE_OK { print("重置失败") return false } // 6.关闭STMT // 注意点: 只要用到了stmt, 一定要关闭 sqlite3_finalize(stmt) return true }
最后奉上练习的demo: SwiftSQLite