一、數(shù)據(jù)庫的簡單介紹
- 數(shù)據(jù)庫(Database)是按照數(shù)據(jù)結(jié)構(gòu)來組織、存儲和管理數(shù)據(jù)的倉庫
- 數(shù)據(jù)庫可以分為2大種類:關(guān)系型數(shù)據(jù)庫(主流)和對象型數(shù)據(jù)庫
- 常用關(guān)系型數(shù)據(jù)庫
- PC 端:Oracle(收費(fèi))、MySQL(國內(nèi)免費(fèi))、SQL Server 、Access、DB2、Sybase
- 嵌入式\移動客戶端:SQLite
二、SQLite的介紹
- SQLite是一款輕型的嵌入式數(shù)據(jù)庫
- 它占用的資源非常的低,在嵌入式的設(shè)備中,可能只需要幾百K的內(nèi)存就夠了
- 它的處理速度比Mysql、PostgreSQL這兩款著名的數(shù)據(jù)庫都還快
三、SQL和SQL語句的介紹以及SQL語句的特點(diǎn)
SQL(structured query language):結(jié)構(gòu)化查詢語言,它還是一種對關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行定義和操作的語言,而且它的語言簡潔,語法簡單,好學(xué)好用。
SQL語句:使用SQL語言編寫出來的句子\代碼,就是SQL語句,在程序運(yùn)行的過程中,要想操作(增刪改查)數(shù)據(jù)庫中的數(shù)據(jù),必須使用SQL語句
-
SQL語句的特點(diǎn)
- 不區(qū)分大小寫(比如數(shù)據(jù)庫的person和PerSon是一樣的)
- 每條語句必須以分號結(jié)尾(;)
-
SQL中的關(guān)鍵字有很多
select、insert、update、delete、from、create、where、desc、order等等
數(shù)據(jù)庫不可以使用關(guān)鍵字來命名表、字段
SQLlite數(shù)據(jù)存儲類型:integer(整型值),real(浮點(diǎn)值),text(文本字符串),blob(二進(jìn)制數(shù)據(jù),例如文件)
四、SQL語句的種類(DDL,DML,DQL)下面的學(xué)習(xí)會涉及到一個(gè)數(shù)據(jù)庫工具Navicat,請到QQ群:584599353的文件庫常用軟件下載,找群主要注冊碼
-
1、數(shù)據(jù)定義語句(DDL: Data Definition Language),包括create和drop等操作,在數(shù)據(jù)庫中建表或刪除表(create table和drop table)
-
具體的使用:
-
1.1、創(chuàng)建表的語句
- IF NOT EXISTS 判斷創(chuàng)建的表是否存在,只有不存在才會創(chuàng)建
- 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、數(shù)據(jù)操作語句(DML: Data Manipulation Language),包括insert、update、delete等操作,上面的三種操作分別用于添加、修改,刪除表中的數(shù)據(jù)
-
2.1、添加(插入數(shù)據(jù):insert)(注意:前后兩個(gè)括號是對應(yīng)的,數(shù)據(jù)庫中的字符串要加單引號)
INSERT INTO T_person (name,age) VALUES ('wc',26); -
2.2、更新數(shù)據(jù)(update)(下面的代碼會把數(shù)據(jù)庫中的所有名字和年齡都改了)
UPDATE T_person SET name = 'jk';更改了所有人名字 UPDATE T_person SET name = '王沖' WHERE age = 30; 更改了年齡等于30人的名字 -
2.3、刪除數(shù)據(jù)(下面的是不對的,會刪除整個(gè)表的)
DELETE FROM T_person; 刪除表中所有的數(shù)據(jù)了 DELETE FROM T_person WHERE age = 28; 刪除了表中年齡等于28的人 -
2.4、條件語句的常見格式
WHERE 字段 = 某個(gè)值; 不可以用兩個(gè)= WHERE 字段 is 某個(gè)值; is 相當(dāng)于 = WHERE 字段 != 某個(gè)值 WHERE 字段 is not 某個(gè)值 is not 相當(dāng)于 != WHERE 字段 > 某個(gè)值; WHERE 字段1 = 某個(gè)值 and 字段2 > 某個(gè)值 // and相當(dāng)于oc里面的&& WHERE 字段1 = 某個(gè)值 or 字段2 = 某個(gè)值 // and相當(dāng)于oc里面的||
-
-
3、數(shù)據(jù)查詢語句(DQL: Data Query Langue),可以查詢獲得表中的數(shù)據(jù),關(guān)鍵字select是DQL(也是左右SQL)用的最多的操作,其他DQL常用的關(guān)鍵字有where,order by,group by 和having
-
3.1、查詢整個(gè)表
SELECT * FROM T_Person2; // T_Person2是表名 -
3.2、查詢符合條件的表中數(shù)據(jù)(年齡大于13的人)
SELECT name,age FROM T_Person2 WHERE age > 13; -
3.3、給字段起別名
SELECT name as nameJ,age as ageK FROM T_Person2;給字段起別名 -
3.4、給表起別名 (同時(shí)查詢不同的表,里面包含相同的字段的時(shí)候)
SELECT table1.name,table1.age,table2.name,table2.age FROM T_Person2 as table1,T_person as table2; -
3.5、計(jì)算記錄的數(shù)量
-
SELECT count(*或者字段) FROM T_Person2(表名) : 查詢表中某一個(gè)字段有多少條
SELECT count(*) FROM T_Person2 -
加約束條件的查詢 (年齡大于20的條數(shù))
SELECT count(*) FROM T_Person2 WHERE age > 20
-
-
3.6、查詢的結(jié)果用
-
單個(gè)字段排序(默認(rèn)是升序
ASC,降序是DESC)SELECT * FROM T_Person2 ORDER BY age DESC -
多個(gè)字段排序 (年齡按照降序排序,遇到年齡相同的,再按降序排序)
SELECT * FROM T_Person2 ORDER BY age DESC , id DESC;
-
-
-
3.6、
LIMIT分頁查詢-
LIMIT數(shù)值1,數(shù)值2
數(shù)值1:代表跳過幾條
數(shù)值2:代表取幾條
-
如果只取一個(gè)數(shù)值代表取前幾條
取前3條 SELECT * FROM T_Person2 LIMIT 3; 跳過前3條取后面的4條 SELECT * FROM T_Person2 LIMIT 3,4; -
如果按照上面的分頁,下面可以用一個(gè)公式概括
10 代表一頁10條數(shù)據(jù) n代表第幾頁 SELECT * FROM T_Person2 LIMIT 10*(n-1),10;
-
3.7、對建表的字段的簡單約束(如下面的方式建立一個(gè)表)
NOT NULL : 規(guī)定字段的值不能為null
UNIQUE: 規(guī)定字段的值不能一樣
-
DEFAULT: 指定的字段設(shè)置默認(rèn)值
CREATE TABLE IF NOT EXISTS T_Person5( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER DEFAULT 20 );
-
-
五、SQLite在Xcode里面的運(yùn)用
5.1、下面會按照創(chuàng)建數(shù)據(jù)庫、建表、插入數(shù)據(jù)、更新數(shù)據(jù)、刪除數(shù)據(jù)、多條數(shù)據(jù)的插入、多條數(shù)據(jù)插入時(shí)間的優(yōu)化(事件的添加)、預(yù)插入數(shù)據(jù)和事件的結(jié)合。
-
5.2、創(chuàng)建一個(gè)單例類
JKSQLiteMangerimport UIKit class JKSQLiteManger: NSObject { private static let manger: JKSQLiteManger = JKSQLiteManger() // 單粒 class func shareManger() -> JKSQLiteManger { return manger; } } -
5.3、創(chuàng)建數(shù)據(jù)庫、建表(在使用之前打開,一般可以放到
AppDelegate里面),下面的\n是為了打印換行方便看,+是為了把上下兩句話連成一句話,數(shù)據(jù)的名字要定義為JK.sqlite的格式,下面T_Person8是表名,name,age:字段名,id:主鍵名private var db: OpaquePointer? = nil // MARK: 打開數(shù)據(jù)庫 func openDB(SQLiteName: String) { // 0.拿到數(shù)據(jù)庫的路徑 let path = String.cacheDir() + "/\(SQLiteName)" print(path) let cPath = path.cString(using: String.Encoding.utf8)! // 1.打開數(shù)據(jù)庫 /** 1.1、cPath: 需要打開的數(shù)據(jù)庫文件的路徑,這里的路徑是C語言字符串的路徑 1.2、打開之后的數(shù)據(jù)庫對象(指針),以后所有的數(shù)據(jù)庫操作,都必須拿到這個(gè)指針才能進(jìn)行相關(guān)的操作 */ // open方法的特點(diǎn):如果指定路徑的數(shù)據(jù)庫文件已經(jīng)存在,就會直接打開,否則就會創(chuàng)建一個(gè)新的 if sqlite3_open(cPath, &db) != SQLITE_OK{ print("打開數(shù)據(jù)庫失敗") return } // 2.創(chuàng)建表 if creatTable(){ print("創(chuàng)建表成功") }else{ print("創(chuàng)建表失敗") } } //MARK: 創(chuàng)建表 func creatTable() -> Bool { // 1、編寫SQL語句 /** 建議:在開發(fā)中編寫SQL語句,如果語句過長,不要寫在一行 技巧:在做數(shù)據(jù)庫開發(fā)時(shí)候,如果遇到錯(cuò)誤,可以先將SQL打印出來,拷貝到PC工具中驗(yàn)證之后再進(jìn)行調(diào)試 */ 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、執(zhí)行SQL語句 // 在SQLite3中,除了查詢以外(創(chuàng)建/刪除/新增/更新)都使用同一個(gè)函數(shù) return execSQL(sql: sql) } -
5.4、插入、更新、刪除 一條數(shù)據(jù)
/** 插入一條數(shù)據(jù)*/ let sql = "INSERT INTO T_Person8 (name,age) VALUES ('\(name!)',\(age));" /** 更新一條數(shù)據(jù)*/ let sql = "UPDATE T_Person8 SET name = '\(name)' WHERE age = \(self.age);" /** 刪除一條數(shù)據(jù)*/ let sql = "DELETE FROM T_Person8 WHERE age = \(self.age);" // MARK:執(zhí)行除查詢以外的SQL語句 /** - Parameter sql: 要執(zhí)行的SQL語句 - Returns: 是否執(zhí)行成功 true:執(zhí)行成功 false:執(zhí)行失敗 */ func execSQL(sql: String) -> Bool { // 1.將Swift的字符串轉(zhuǎn)換為C語言的字符串 let cSQL = sql.cString(using: String.Encoding.utf8)! // 在SQLite3中,除了查詢以外(創(chuàng)建/刪除/新增/更新)都使用同一個(gè)函數(shù) /** 1、已經(jīng)打開的數(shù)據(jù)庫對象 2、需要執(zhí)行的SQLite語句,C語言字符串 3、執(zhí)行SQLite語句后面的回調(diào),一般傳nil 4、第三個(gè)參數(shù)的第一個(gè)參數(shù),一般傳nil 5、錯(cuò)誤信息一般傳nil */ if sqlite3_exec(db, cSQL, nil, nil, nil) != SQLITE_OK{ return false } return true } -
5.5、查詢數(shù)據(jù)
let sql = "SELECT * FROM T_Person8;" let res = JKSQLiteManger.shareManger().execRecordSQL(sql: sql) // MARK:查詢所有的數(shù)據(jù) func execRecordSQL(sql: String) ->[[String: AnyObject]] { // 0.將Swift字符串轉(zhuǎn)換為C語言字符串 let cSQL = sql.cString(using: String.Encoding.utf8)! // 1.準(zhǔn)備數(shù)據(jù) // 準(zhǔn)備: 理解為預(yù)編譯SQL語句, 檢測里面是否有錯(cuò)誤等等, 它可以提供性能 /* 1.已經(jīng)開打的數(shù)據(jù)庫對象 2.需要執(zhí)行的SQL語句 3.需要執(zhí)行的SQL語句的長度, 傳入-1系統(tǒng)自動計(jì)算 4.預(yù)編譯之后的句柄, 已經(jīng)要想取出數(shù)據(jù), 就需要這個(gè)句柄 5. 一般傳nil */ var stmt: OpaquePointer? = nil if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK { print("準(zhǔn)備失敗") } // 準(zhǔn)備成功 var records = [[String: AnyObject]]() // 2.查詢數(shù)據(jù) // sqlite3_step代表取出一條數(shù)據(jù), 如果取到了數(shù)據(jù)就會返回SQLITE_ROW while sqlite3_step(stmt) == SQLITE_ROW { // 獲取一條記錄的數(shù)據(jù) let record = recordWithStmt(stmt: stmt!) // 將當(dāng)前獲取到的這一條記錄添加到數(shù)組中 records.append(record) } // 3.關(guān)閉STMT // 注意點(diǎn): 只要用到了stmt, 一定要關(guān)閉 sqlite3_finalize(stmt) // 返回查詢到的數(shù)據(jù) return records } /** 獲取一條記錄的值 :param: stmt 預(yù)編譯好的SQL語句 :returns: 字典 */ private func recordWithStmt(stmt: OpaquePointer) ->[String: AnyObject] { // 2.1拿到當(dāng)前這條數(shù)據(jù)所有的列 let count = sqlite3_column_count(stmt) // print(count) // 定義字典存儲查詢到的數(shù)據(jù) 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: // 浮點(diǎn)型 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: // 二進(jìn)制類型 SQLITE_BLOB // 一般情況下, 不會往數(shù)據(jù)庫中存儲二進(jìn)制數(shù)據(jù) print("") } } return record } -
5.6、大批量數(shù)據(jù)插入的優(yōu)化問題(下面將使用事務(wù)和 異步串行的隊(duì)列來快速的插入數(shù)據(jù))
// MARK: 事務(wù)相關(guān) // 1.開啟事務(wù) func beginTransaction(){ execSQL(sql: "BEGIN TRANSACTION") } // 2.提交事務(wù) func commitTransaction(){ execSQL(sql: "COMMIT TRANSACTION") } // 3.回滾 func rollbackTransaction(){ execSQL(sql: "ROLLBACK TRANSACTION") } // MARK: 創(chuàng)建一個(gè)異步串行隊(duì)列來執(zhí)行數(shù)據(jù)的插入,防止界面卡頓 private let dbQueue = DispatchQueue(label: "com.520it.lnj") func execQueueSQL(action:@escaping (_ manager: JKSQLiteManger)->()) { // 1.開啟一個(gè)子線程 dbQueue.async { //print(Thread.current) // 2.執(zhí)行閉包 action(self) } } -
5.7、預(yù)編譯優(yōu)化數(shù)據(jù)庫
// MARK: 預(yù)編譯優(yōu)化數(shù)據(jù)庫 func batchExecSQL() { let start = CFAbsoluteTimeGetCurrent() let manager = JKSQLiteManger.shareManger() // 開啟事務(wù) 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) } // 提交事務(wù) manager.commitTransaction() print("耗時(shí) = \(CFAbsoluteTimeGetCurrent() - start)") } // 自定義一個(gè)SQLITE_TRANSIENT, 覆蓋系統(tǒng)的 private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) // MARK: - 預(yù)編譯優(yōu)化數(shù)據(jù)庫 func batchExecSQL(sql:String, args: CVarArg...) -> Bool { // 1.將SQL語句轉(zhuǎn)換為C語言 let cSQL = sql.cString(using: String.Encoding.utf8)! // 2.預(yù)編譯SQL語句 var stmt: OpaquePointer? = nil if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK { print("預(yù)編譯失敗") sqlite3_finalize(stmt) return false } // 3.綁定數(shù)據(jù) var index:Int32 = 1 for objc in args { if objc is Int { //print("通過int方法綁定數(shù)據(jù) \(objc)") // 第二個(gè)參數(shù)就是SQL中('?', ?)的位置, 注意: 從1開始 sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int)) }else if objc is Double { //print("通過Double方法綁定數(shù)據(jù) \(objc)") sqlite3_bind_double(stmt, index, objc as! Double) }else if objc is String { // print("通過Text方法綁定數(shù)據(jù) \(objc)") let text = objc as! String let cText = text.cString(using: String.Encoding.utf8)! // 第三個(gè)參數(shù): 需要綁定的字符串, C語言 // 第四個(gè)參數(shù): 第三個(gè)參數(shù)的長度, 傳入-1系統(tǒng)自動計(jì)算 // 第五個(gè)參數(shù): OC中直接傳nil, 但是Swift傳入nil會有大問題 /* typedef void (*sqlite3_destructor_type)(void*); #define SQLITE_STATIC ((sqlite3_destructor_type)0) #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) 第五個(gè)參數(shù)如果傳入SQLITE_STATIC/nil, 那么系統(tǒng)不會保存需要綁定的數(shù)據(jù), 如果需要綁定的數(shù)據(jù)提前釋放了, 那么系統(tǒng)就隨便綁定一個(gè)值 第五個(gè)參數(shù)如果傳入SQLITE_TRANSIENT, 那么系統(tǒng)會對需要綁定的值進(jìn)行一次copy, 直到綁定成功之后再釋放 */ sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT) } index = index + 1 } // 4.執(zhí)行SQL語句 if sqlite3_step(stmt) != SQLITE_DONE { print("執(zhí)行SQL語句失敗") return false } // 5.重置STMT if sqlite3_reset(stmt) != SQLITE_OK { print("重置失敗") return false } // 6.關(guān)閉STMT // 注意點(diǎn): 只要用到了stmt, 一定要關(guān)閉 sqlite3_finalize(stmt) return true }
最后奉上練習(xí)的demo: SwiftSQLite
