IOS DB技术框架对比

2019-10-17  本文已影响0人  孔雨露

@[TOC](IOS DB技术框架对比)

1. 数据库简介

  1. 关系型数据库,代表有CoreData、FMDB等。
  2. key-value数据库,代表有Realm、LevelDB、RocksDB等。

它是苹果内建框架,和Xcode深度结合,可以很方便进行ORM;但其上手学习成本较高,不容易掌握。稳定性也堪忧,很容易crash;多线程的支持也比较鸡肋。

它基于SQLite封装,对于有SQLite和ObjC基础的开发者来说,简单易懂,可以直接上手;而缺点也正是在此,FMDB只是将SQLite的C接口封装成了ObjC接口,没有做太多别的优化,即所谓的胶水代码(Glue Code)。使用过程需要用大量的代码拼接SQL、拼装Object,并不方便。

因其在各平台封装、优化的优势,比较受移动开发者的欢迎。对于iOS开发者,key-value的实现直接易懂,可以像使用NSDictionary一样使用Realm。并且ORM彻底,省去了拼装Object的过程。但其对代码侵入性很强,Realm要求类继承RLMObject的基类。这对于单继承的ObjC,意味着不能再继承其他自定义的子类。同时,key-value数据库对较为复杂的查询场景也比较无力。

1.1 WCDB-iOS/Mac

WCDB-iOS/Mac(以下简称WCDB](https://github.com/Tencent/wcdb),均指代WCDB的iOS/Mac版本),是一个基于SQLite封装的Objective-C++数据库组件,提供了如下功能:

WCDB覆盖了数据库使用的绝大部分场景,且经过微信海量用户的验证,并将持续不断地增加新的能力。

具体WCDB使用可以参考这两篇博客:

  1. WCDB OC使用
  2. WCDB.swift使用

2. 数据库 Realm、WCDB, SQLite性能对比

2.1 测试数据表结构

Student表。

字段:ID、name、age、money。

ID name age money
主键 姓名 年龄 存款(建索引)

其中age为0100随机数字,money为每一万条数据中,010000各个数字只出现一次。

2.2 测试数据

对于以下测试数据,只是给出一次测试后的具体数值供参考,经过反复测试后的,基本都在这个时间量级上。

这里测试用的是纯SQLite,没有用FMDB。

2.2.1 SQLite3

2.2.2 realm

2.2.3 WCDB

2.2.4 三者对比

测试内容 Realm WCDB SQLite 用例数量
单条插入一万条 32851ms 750ms 1462ms 90000+10000
循环查询一万次 699ms 690ms 331ms 100000
100个block查询一万次 205ms 199ms 186ms 100000
Realm、WCDB与SQLite移动数据库性能对比测试
对比2

3. WCDB, FMDB性能对比

WCDB.swift和fmdb做对比 WCDB.swift和fmdb做对比

4. 数据库框架优缺点对比

4.1 SQLite 优缺点

优点
  1. SQLite是轻量级的,没有客户端和服务器端之分,并且是跨平台的关系型数据库。
  2. SQLite是一个单文件的,可以copy出来在其他地方用。
  3. 有一个SQLite.swift框架非常好用。
缺点
  1. SQLite在并发的读写方面性能不是很好,数据库有时候可能会被某个读写操作独占,可能会导致其他的读写操作被阻塞或者出错。
  2. 不支持SQL92标准,有时候语法不严格也可以通过,会养成不好习惯,导致不会维护。
  3. 需要写很多SQL拼接语句,写很多胶水代码,容易通过SQL注入恶意代码。
  4. 效率很低:SQL基于字符串,命令行爱好者甚喜之。但对于基于现代IDE的移动开发者,却是一大痛。字符串得不到任何编译器的检查,业务开发往往心中一团热火,奋笔疾书下几百行代码,满心欢喜点下Run后才发现:出错了!静心下来逐步看log、断点后才发现,噢,SELECT敲成SLEECT了。改正,再等待编译完成,此时已过去十几分钟。

4.2 FMDB 优缺点

优点
  1. 它基于SQLite封装,对于有SQLite和ObjC基础的开发者来说,
  2. 简单易懂,可以直接上手;
缺点
  1. FMDB只是将SQLite的C接口封装成了ObjC接口,没有做太多别的优化,即所谓的胶水代码(Glue Code)。
  2. 使用过程需要用大量的代码拼接SQL、拼装Object,并不方便。
  3. 容易通过SQL代码注入。
  4. 直接暴露字符串接口,让业务开发自己拼接字符串,取出数据后赋值给对应的Object. 这种方式过于简单粗暴。
官方文档

4.3 CoreData 优缺点

优点

它是苹果内建框架,和Xcode深度结合,可以很方便进行ORM;

缺点
  1. 其上手学习成本较高,不容易掌握。
  2. 稳定性也堪忧,很容易crash;多线程的支持也比较鸡肋。

4.4 Realm优缺点

优点
  1. Realm在使用上和Core Data有点像,直接建立我们平常的对象Model类就是建立一个表了,确定主键、建立索引也在Model类里操作,几行代码就可以搞定,在操作上也可以很方便地增删改查,不同于SQLite的SQL语句(即使用FMDB封装的操作依然有点麻烦),Realm在日常使用上非常简单,起码在这次测试的例子中两个数据库同样的一些操作,Realm的代码只有SQLite的一半。
  2. 其实Realm的“表”之间也可以建立关系,对一、对多关系都可以通过创建属性来解决。
  3. 在.m方法中给“表”确定主键、属性默认值、加索引的字段等。
  4. 修改数据时,可以直接丢进去一条数据,Realm会根据主键判断是否有这个数据,有则更新,没有则添加。
  5. 查询操作太简单了,一行代码根据查询目的来获取查询结果的数组。
  6. 支持KVC和KVO。
  7. 支出数据库加密。
  8. 支持通知。
  9. 方便进行数据库变更(版本迭代时可能发生表的新增、删除、结构变化),Realm会自行监测新增加和需要移除的属性,然后更新硬盘上的数据库架构,Realm可以配置数据库版本,进行判断。
  10. 一般来说Realm比SQLite在硬盘上占用的空间更少。
缺点
  1. Realm也有一些限制,需要考虑是否会影响。
  2. 类名长度最大57个UTF8字符。
  3. 属性名长度最大63个UTF8字符。
  4. NSData及NSString属性不能保存超过16M数据,如果有大的可以分块。
  5. 对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。
  6. 多线程访问时需要新建新的Realm对象。
  7. Realm没有自增属性。。也就是说对于我们习惯的自增主键,如果确实需要,我们要自己去赋值,如果只要求独一无二, 那么可以设为[[NSUUID UUID] UUIDString],如果还要求用来判断插入的顺序,那么可以用Date。
  8. Realm支持以下的属性类型:BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData以及 被特殊类型标记的NSNumber,注意,不支持集合类型,只有一个集合RLMArray,如果服务器传来的有数组,那么需要我们自己取数据进行转换存储。
官方文档

4.5 WCDB优缺点

优点
  1. 实际体验后,WCDB的代码体验非常好,代码量基本等于Realm,都是SQLite的一半,
  2. 在风格上比Realm更接近于OC原本的风格,基本已经感受不到是在写数据库的SQL操作。并且其查询语句WINQ也写的很符合逻辑,基本都可以一看就懂,甚至不需要你了解SQL语句。
  3. 整个开发流程下来非常流畅,除了配置环境时出了问题并且没有资料参考只能自己猜着解决外,代码基本是一气呵成写完完美运行的。
  4. WCDB通过ORM和WINQ,体现了其易用性上的优势,使得数据库操作不再繁杂。同时,通过链式调用,开发者也能够方便地获取数据库操作的耗时等性能信息。
  1. one line of code 是它坚持的原则,大多数操作只需要一行代码即可完成.
  2. 使用WINQ 语句查询,不用为拼接SQL语句而烦恼了,模型绑定映射也是按照规定模板去实现方便快捷。
  1. 支持基于SQLCipher 加密
  2. 持全文搜索
  3. 支持反注入,可以避免第三方从输入框注入 SQL,进行预期之外的恶意操作。
  4. 用户不用手动管理数据库字段版本,升级方便自动.
  5. 提供数据库修复工具。
缺点
  1. 最明显的缺点是其相关资料太少了

贴一份评论


贴一份评论
官方文档

5. 总结

  1. 个人比较推荐使用微信的WCDB框架,这个框架是开源的,如果有需要一定要自己拼接SQL语句,要实现SQL语句的扩展也是很容易的事情。
  2. 在选型上,每个框架都有自己的优缺点,并没有却对的优劣性,只有适不适合项目需求。其实对于小型项目直接使用Sqlite或者用FMDB 都可以满足要求,但是如果遇到安全性问题,需要自己重复造很多轮子实现加密等功能。在使用上面如果直接使用SQL 语句,像在Jimu1.0里面SQL语句到处散乱,出了问题不好定位,需要写很多重复的拼接SQL语句的胶水代码。而且SQL语句如果写错了编译并不会报错或警告,如果出现了因为SQL语句的bug,到项目后期很难定位。
  3. FMDB的SQL拼接、难以防止的SQL注入;CoreData虽然可以方便ORM,但学习成本高,稳定性堪忧,而且多线程鸡肋;另外基于C语言的sqlite我想用的人也应该不多;除了上述关系型数据库之外然后还有一些其他的Key-Value型数据库,如我用过的Realm,对于ObjC开发者来说,上手倒是没什么难度,但缺点显而易见,需要继承,入侵性强,对于单继承的OC来说这并不理想,而且对于集合类型不完全支持,复杂查询也比较无力。
  4. WCDB是微信团队于2017年6月9日开源的。开源时间不长。可能相关资料比较少,只能靠查看官方文档。
  5. SQLite3直接使用比较麻烦,而FMDB是OC编写的,如果使用Swift版本推荐使用:SQLite.swift这是很好的框架比使用FMDB简单,代码简介很多。SQLite.swift对SQLite进行了全面的封装,拥有全面的纯swift接口,即使你不会SQL语句,也可以使用数据库。作者采用了链式编程的写法,让数据库的管理变得优雅,可读性也很强。
  1. 最佳方案A是使用WCDB.swift框架。
  2. 方案B是使用SQLite.swift
  3. 方案C是使用 Realm 框架
  4. 方案D是使用 FMDB 框架

6. 简单对比WCDB.swift,Realm.swift,SQLite.swift的用法

6.1 WCDB.swift基本用法

6.1.0 新建一个模型

import Foundation
import WCDBSwift

class Sample: TableCodable {
    var identifier: Int? = nil
    var description: String? = nil
    
    enum CodingKeys: String, CodingTableKey {
        typealias Root = Sample
        static let objectRelationalMapping = TableBinding(CodingKeys.self)
        case identifier
        case description
        
        static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? {
            return [
                identifier: ColumnConstraintBinding(isPrimary: true),
            ]
        }
    }
}

6.1.1 创建数据库

private lazy var db : Database? = {
        //1.创建数据库
        let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/wcdb.db"
        let database = Database(withPath: docPath + "/wcdb.db")
        return database
    }()

6.1.2 创建数据库表

private func testCreateTable() {
        guard let db = db else {
            return
        }
        do {
            //创建数据库表
            try db.create(table: TB_Sample, of: Sample.self)
            
        } catch {
            print(error)
        }
    }

6.1.3 插入操作

private func testInsert() {
        guard let db = db else {
            return
        }
        do {
            //插入数据库
            let object = Sample()
            object.identifier = 1
            object.description = "insert"
            try db.insert(objects: object, intoTable: TB_Sample) // 插入成功
            
            try db.insert(objects: object, intoTable: TB_Sample) // 插入失败,因为主键 identifier = 1 已经存在
            
            object.description = "insertOrReplace"
            try db.insertOrReplace(objects: object, intoTable: TB_Sample) // 插入成功,且 description 的内容会被替换为 "insertOrReplace"
            
        } catch {
            print(error)
        }
    }

6.1.4 删除操作

private func testDelete() {
        guard let db = db else {
            return
        }
        do {
            //删除操作
            // 删除 sampleTable 中所有 identifier 大于 1 的行的数据
            try db.delete(fromTable: TB_Sample,
                          where: Sample.Properties.identifier > 1)
            
            // 删除 sampleTable 中的所有数据
            try db.delete(fromTable: TB_Sample)
            
        } catch {
            print(error)
        }
    }

6.1.5 更新操作

private func testUpdate() {
        guard let db = db else {
            return
        }
        do {
            //更新数据
            let object = Sample()
            object.description = "update"
            
            // 将 sampleTable 中前三行的 description 字段更新为 "update"
            try db.update(table: TB_Sample,
                          on: Sample.Properties.description,
                          with: object,
                          limit: 3)
            
        } catch {
            print(error)
        }
    }

6.1.6 查询操作

 private func testQuery() {
        guard let db = db else {
            return
        }
        do {
            //查询操作
            // 返回 sampleTable 中的所有数据
            let allObjects: [Sample] = try db.getObjects(fromTable: TB_Sample)
            
            print(allObjects)
            
            // 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
            let objects: [Sample] = try db.getObjects(fromTable: TB_Sample,
                                                            where: Sample.Properties.identifier < 5 || Sample.Properties.identifier > 10)
            print(objects)
            
            // 返回 sampleTable 中 identifier 最大的行的数据
//            let object: Sample? = try db.getObject(fromTable: TB_Sample,
//                                                         orderBy: Sample.Properties.identifier.asOrder(by: .descending))
            
            // 获取所有内容
            let allRows = try db.getRows(fromTable: TB_Sample)
            print(allRows[row: 2, column: 0].int32Value) // 输出 3
            
            // 获取第二行
            let secondRow = try db.getRow(fromTable: TB_Sample, offset: 1)
            print(secondRow[0].int32Value) // 输出 2
            
            // 获取 description 列
            let descriptionColumn = try db.getColumn(on: Sample.Properties.description, fromTable: TB_Sample)
            print(descriptionColumn) // 输出 "sample1", "sample1", "sample1", "sample2", "sample2"
            
            // 获取不重复的 description 列的值
            let distinctDescriptionColumn = try db.getDistinctColumn(on: Sample.Properties.description, fromTable: TB_Sample)
            print(distinctDescriptionColumn) // 输出 "sample1", "sample2"
            
            // 获取第二行 description 列的值
            let value = try db.getValue(on: Sample.Properties.description, fromTable: TB_Sample, offset: 1)
            print(value.stringValue) // 输出 "sample1"
            
            // 获取 identifier 的最大值
            let maxIdentifier = try db.getValue(on: Sample.Properties.identifier.max(), fromTable: TB_Sample)
            print(maxIdentifier.stringValue)
            
            // 获取不重复的 description 的值
            let distinctDescription = try db.getDistinctValue(on: Sample.Properties.description, fromTable: TB_Sample)
            print(distinctDescription.stringValue) // 输出 "sample1"
            
        } catch {
            print(error)
        }
    }

6.2 Realm.swift基本用法

6.2.1 创建数据库

import RealmSwift

static let sharedInstance = try! Realm()
    
    static func initRealm() {
        
        var config = Realm.Configuration()
        //使用默认的目录,但是可以使用用户名来替换默认的文件名
        config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("Bilibili.realm")
        //获取我们的Realm文件的父级目录
        let folderPath = config.fileURL!.deletingLastPathComponent().path
        //解除这个目录的保护
        try! FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.none], ofItemAtPath: folderPath)
        //创建Realm
        Realm.Configuration.defaultConfiguration = config
    }

6.2.2 创建数据库表

static func add<T: Object>(_ object: T) {
        try! sharedInstance.write {
            sharedInstance.add(object)
        }
    }

6.2.3 插入操作

/// 添加一条数据
    static func addCanUpdate<T: Object>(_ object: T) {
        try! sharedInstance.write {
            sharedInstance.add(object, update: true)
        }
    }
static func addListData<T: Object>(_ objects: [T]) {
        autoreleasepool {
            // 在这个线程中获取 Realm 和表实例
            let realm = try! Realm()
            // 批量写入操作
            realm.beginWrite()
            // add 方法支持 update ,item 的对象必须有主键
            for item in objects {
                realm.add(item, update: true)
            }
            // 提交写入事务以确保数据在其他线程可用
            try! realm.commitWrite()
        }
    }
static func addListDataAsync<T: Object>(_ objects: [T]) {
        
        let queue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        // Import many items in a background thread
        queue.async {
            // 为什么添加下面的关键字,参见 Realm 文件删除的的注释
            autoreleasepool {
                // 在这个线程中获取 Realm 和表实例
                let realm = try! Realm()
                // 批量写入操作
                realm.beginWrite()
                // add 方法支持 update ,item 的对象必须有主键
                for item in objects {
                    realm.add(item, update: true)
                }
                // 提交写入事务以确保数据在其他线程可用
                try! realm.commitWrite()
            }
        }
    }

6.2.4 删除操作

/// 删除某个数据
    static func delete<T: Object>(_ object: T) {
        try! sharedInstance.write {
            sharedInstance.delete(object)
        }
    }

6.2.5 更新操作

 /// 添加一条数据
    static func addCanUpdate<T: Object>(_ object: T) {
        try! sharedInstance.write {
            sharedInstance.add(object, update: true)
        }
    }
    static func addListData<T: Object>(_ objects: [T]) {
        autoreleasepool {
            // 在这个线程中获取 Realm 和表实例
            let realm = try! Realm()
            // 批量写入操作
            realm.beginWrite()
            // add 方法支持 update ,item 的对象必须有主键
            for item in objects {
                realm.add(item, update: true)
            }
            // 提交写入事务以确保数据在其他线程可用
            try! realm.commitWrite()
        }
    }

6.2.6 查询操作

/// 根据条件查询数据
    static func selectByNSPredicate<T: Object>(_: T.Type , predicate: NSPredicate) -> Results<T>{
        return sharedInstance.objects(T.self).filter(predicate)
    }
    /// 后台根据条件查询数据
    static func BGselectByNSPredicate<T: Object>(_: T.Type , predicate: NSPredicate) -> Results<T>{
        return try! Realm().objects(T.self).filter(predicate)
    }
    /// 查询所有数据
    static func selectByAll<T: Object>(_: T.Type) -> Results<T>{
        return sharedInstance.objects(T.self)
    }
    
    /// 查询排序后所有数据,关键词及是否升序
    static func selectScoretByAll<T: Object>(_: T.Type ,key: String, isAscending: Bool) -> Results<T>{
        return sharedInstance.objects(T.self).sorted(byKeyPath: key, ascending: isAscending)
    }

6.3 FMDB基本用法

6.4 SQLite.swift基本用法

SQLite.swift对SQLite进行了全面的封装,拥有全面的纯swift接口,即使你不会SQL语句,也可以使用数据库。作者采用了链式编程的写法,让数据库的管理变得优雅,可读性也很强。

  1. Carthage:
github "stephencelis/SQLite.swift"
  1. CocoaPods:
pod 'SQLite.swift'
  1. Swift Package Manager:
dependencies: [
    .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5")
]

6.4.1 创建数据库

import SQLite

let path = NSSearchPathForDirectoriesInDomains(
                .documentDirectory, .userDomainMask, true
                ).first!
let db = try! Connection("\(path)/db.sqlite3")
  1. 这只是最简单的方式,我们来深入看一下Connection的初始化方法和可以设置的参数:
    public init(_ location: SQLite.Connection.Location = default, readonly: Bool = default) throws
  2. 第一个参数Location指的是数据库的位置,有三种情况:
    inMemory数据库存在内存里;temporary临时数据库,使用完会被释放掉;filename (or path)存在硬盘中,我们上面用的就是这种。前两种使用完毕会被释放不会保存,第三种可以保存下来;第一种数据库存在内存中,后两种存在硬盘里。
  3. readonly数据库是否为只读不可修改,默认为false。只读的情况一般是我们复制一个数据库文件到我们的项目,只读取数据使用,不做修改。

使用数据库避免不了多线程操作,SQLite.swift中我们有两个选项可以设置

db.busyTimeout = 5.0

db.busyHandler({ tries in
    if tries >= 5 {
        return false
    }
    return true
})

6.4.2 创建数据库表

let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")

try db.run(users.create { t in
    t.column(id, primaryKey: true)
    t.column(name)
    t.column(email, unique: true)
})

等价于执行SQL:

// CREATE TABLE "users" (
//     "id" INTEGER PRIMARY KEY NOT NULL,
//     "name" TEXT,
//     "email" TEXT NOT NULL UNIQUE
// )

此外还可以这样创建:

let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")

try db.run(users.create(temporary: false, ifNotExists: true, withoutRowid: false, block: { (t) in
                    
    t.column(id, primaryKey: true)
    t.column(name)
    t.column(email, unique: true)
                    
   })
)
/*
temporary:是否是临时表
ifNotExists:是否不存在的情况才会创建,记得设置为true
withoutRowid: 是否自动创建自增的rowid
*/

6.4.3 插入操作

let insert = users.insert(name <- "Alice", email <- "alice@mac.com")
if let rowId = try? db.run(insert) {
            print("插入成功:\(rowId)")
        } else {
            print("插入失败")
        }
//等价于执行下面SQL
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com')

插入成功会返回对应的rowid

6.4.4 删除操作

let alice = users.filter(id == rowid)
if let count = try? db.run(alice.delete()) {
    print("删除的条数为:\(count)")
} else {
    print("删除失败")
}
//等价于执行下面SQL
// DELETE FROM "users" WHERE ("id" = 1)

删除成功会返回删除的行数int值

6.4.5 更新操作

let alice = users.filter(id == rowid)

try db.run(alice.update(email <- email.replace("mac.com", with: "me.com")))
//等价于执行下面SQL
// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com')
// WHERE ("id" = 1)

//可以直接这样
if let count = try? db.run(alice. update()) {
    print("修改的条数为:\(count)")
} else {
    print("修改失败")
}

6.4.6 查询操作

let query = users.filter(name == "Alice").select(email).order(id.desc).limit(l, offset: 1)
for user in try db.prepare(query) {
    print("email: \(user[email])")
    //email: alice@mac.com
}


for user in try db.prepare(users) {
    print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
    // id: 1, name: Optional("Alice"), email: alice@mac.com
}
//等价于执行下面SQL
// SELECT * FROM "users"

let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)")
for email in ["betty@icloud.com", "cathy@icloud.com"] {
    try stmt.run(email)
}

db.totalChanges    // 3
db.changes         // 1
db.lastInsertRowid // 3

for row in try db.prepare("SELECT id, email FROM users") {
    print("id: \(row[0]), email: \(row[1])")
    // id: Optional(2), email: Optional("betty@icloud.com")
    // id: Optional(3), email: Optional("cathy@icloud.com")
}

try db.scalar("SELECT count(*) FROM users") // 2

6.4.7 封装代码

import UIKit
import SQLite
import SwiftyJSON

let type_column = Expression<Int>("type")
let time_column = Expression<Int>("time")
let year_column = Expression<Int>("year")
let month_column = Expression<Int>("month")
let week_column = Expression<Int>("week")
let day_column = Expression<Int>("day")
let value_column = Expression<Double>("value")
let tag_column = Expression<String>("tag")
let detail_column = Expression<String>("detail")
let id_column = rowid

class SQLiteManager: NSObject {
    
    static let manager = SQLiteManager()
    private var db: Connection?
    private var table: Table?
    
    func getDB() -> Connection {
        
        if db == nil {
            
            let path = NSSearchPathForDirectoriesInDomains(
                .documentDirectory, .userDomainMask, true
                ).first!
            db = try! Connection("\(path)/db.sqlite3")
            db?.busyTimeout = 5.0
            
        }
        return db!
        
    }
    
    func getTable() -> Table {
        
        if table == nil {
            
            table = Table("records")
            
            try! getDB().run(
                table!.create(temporary: false, ifNotExists: true, withoutRowid: false, block: { (builder) in
                    
                    builder.column(type_column)
                    builder.column(time_column)
                    builder.column(year_column)
                    builder.column(month_column)
                    builder.column(week_column)
                    builder.column(day_column)
                    builder.column(value_column)
                    builder.column(tag_column)
                    builder.column(detail_column)
                    
                })
            )
            
        }
        return table!
        
    }
    
    //增
    func insert(item: JSON) {
        
        let insert = getTable().insert(type_column <- item["type"].intValue, time_column <- item["time"].intValue, value_column <- item["value"].doubleValue, tag_column <- item["tag"].stringValue , detail_column <- item["detail"].stringValue, year_column <- item["year"].intValue, month_column <- item["month"].intValue, week_column <- item["week"].intValue, day_column <- item["day"].intValue)
        if let rowId = try? getDB().run(insert) {
            print_debug("插入成功:\(rowId)")
        } else {
            print_debug("插入失败")
        }
        
    }
    
    //删单条
    func delete(id: Int64) {
        
        delete(filter: rowid == id)
        
    }
    
    //根据条件删除
    func delete(filter: Expression<Bool>? = nil) {
        
        var query = getTable()
        if let f = filter {
            query = query.filter(f)
        }
        if let count = try? getDB().run(query.delete()) {
            print_debug("删除的条数为:\(count)")
        } else {
            print_debug("删除失败")
        }
        
    }
    
    //改
    func update(id: Int64, item: JSON) {
        
        let update = getTable().filter(rowid == id)
        if let count = try? getDB().run(update.update(value_column <- item["value"].doubleValue, tag_column <- item["tag"].stringValue , detail_column <- item["detail"].stringValue)) {
            print_debug("修改的结果为:\(count == 1)")
        } else {
            print_debug("修改失败")
        }
        
    }
    
    //查
    func search(filter: Expression<Bool>? = nil, select: [Expressible] = [rowid, type_column, time_column, value_column, tag_column, detail_column], order: [Expressible] = [time_column.desc], limit: Int? = nil, offset: Int? = nil) -> [Row] {
        
        var query = getTable().select(select).order(order)
        if let f = filter {
            query = query.filter(f)
        }
        if let l = limit {
            if let o = offset{
                query = query.limit(l, offset: o)
            }else {
                query = query.limit(l)
            }
        }
        
        let result = try! getDB().prepare(query)
        return Array(result)
        
    }
    
}
let inV = SQLiteManager.manager.search(filter: year_column == year && month_column == month && type_column == 1, 
select: [value_column.sum]).first?[value_column.sum] ?? 0.0
//计算year年month月type为1的所有value的和

上一篇 下一篇

猜你喜欢

热点阅读