swift微博第25天(SQLite)

作者: IIronMan | 来源:发表于2018-05-05 19:54 被阅读31次
一、数据库的简单介绍
  • 数据库(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

相关文章

网友评论

    本文标题:swift微博第25天(SQLite)

    本文链接:https://www.haomeiwen.com/subject/cucrxftx.html