swift微博第25天(SQLite)

一、數(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è)單例類 JKSQLiteManger

        import 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容