在 Core Data 中,开发者常常需求面临查询记载数量(count),运用 count 作为谓词或排序条件等需求。本文将介绍在 Core Data 下查询和运用 count 的多种办法,适用于不同的场景。

原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】

一、经过 countResultType 查询 count 数据

本办法为最直接的查询记载条数的办法。经过将 NSFetchQuest 的 resultType 设置为 countResultType,能够直接获取到数据的 count 成果。

let fetchRequest = NSFetchRequest<NSNumber>(entityName: "Item")
fetchRequest.resultType = .countResultType
let count = (try? viewContext.fetch(fetchRequest).first)?.intValue ?? 0
print(count)
/*
 CoreData: sql: SELECT COUNT(*) FROM ZITEM
 CoreData: annotation: total count request execution time: 0.0002s for count of 190.
 190
 */

上文代码中的注释部分,为 Core Data 语句对应的 SQL 命令(运用 -com.apple.CoreData.SQLDebug 1 生成)。详细的设置办法,请参阅 Core Data with CloudKit(四)—— 调试、测试、搬迁及其他

二、运用保管目标上下文的 count 办法查询 count 数据

办法一的快捷版别。调用保管目标上下文供给的 count 办法,回来值类型为 Int。

let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
let count = (try? viewContext.count(for: fetchRequest)) ?? 0
print(count)
/*
 CoreData: sql: SELECT COUNT(*) FROM ZITEM
 CoreData: annotation: total count request execution time: 0.0002s for count of 190.
 190
 */

办法二和办法一对应着完全一致的 SQL 命令。

在仅需获取 count 的情况下(不关心数据的详细内容),办法一和办法二是很好的选择。

三、从成果调会集获取 count 数据

有时在获取数据集之后想一起查看数据集的 count,能够直接运用调集的 count 办法来完结。

let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.predicate = NSPredicate(format: "%K > %@", #keyPath(Item.timestamp), Date.now as CVarArg)
let items = (try? viewContext.fetch(fetchRequest)) ?? []
let count = items.count
print(count)
/*
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP FROM ZITEM t0 WHERE  t0.ZTIMESTAMP > ?
 CoreData: annotation: sql connection fetch time: 0.0001s
 CoreData: annotation: total fetch execution time: 0.0002s for 0 rows.
 */

调用 count 并不会出发导致数据的惰值填充。

在 SwiftUI 下,运用@FetchRequest 获取的成果集,也能够运用上述办法。

假如设置了 fetchLimit ,可能无法取得正确的 count 成果。设置 fetchLimit 后将只回来不超越设定数量的成果。

四、获取单条记载某对多联系的 count 数据

假如你的目标模型中设置了对多联系,调用联系特点的 count 办法,能够获取单条记载某对多联系的目标数量。

let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
let items = (try? viewContext.fetch(fetchRequest)) ?? []
let firstItemTagsCount = items.first?.attachments?.count ?? 0 // 计算联系的数量,将导致本条记载被填充
print(firstItemTagsCount)
/*
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP FROM ZITEM t0
 CoreData: annotation: sql connection fetch time: 0.0002s
 CoreData: annotation: total fetch execution time: 0.0004s for 190 rows.
 CoreData: sql: SELECT 0, t0.Z_PK FROM Z_1TAGS t1 JOIN ZTAG t0 ON t0.Z_PK = t1.Z_2TAGS WHERE t1.Z_1ITEMS = ?
 CoreData: annotation: sql connection fetch time: 0.0001s
 CoreData: annotation: total fetch execution time: 0.0001s for 0 rows.
 CoreData: annotation: to-many relationship fault "tags" for objectID 0xa7ab2d44ebb9106e <x-coredata://0783522F-1851-4BC7-AE0D-AB4C83489E8B/Item/p1> fulfilled from database.  Got 0 rows
 */

上面的代码将获取第一条记载中对多联系 attachments 的 count 数据。此例中,调用 count 办法将会导致 Core Data 为第一条记载填充数据,然后脱离惰值状态。

能够经过设置 relationshipKeyPathsForPrefetching 来调整填充机遇。下面的代码,即使调用 count 办法,也并不会对数据进行填充。

let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.relationshipKeyPathsForPrefetching = ["attachments"]
let items = (try? viewContext.fetch(fetchRequest)) ?? []
let firstItemTagsCount = items.first?.attachments?.count ?? 0 // 计算联系的数量,提前加载 relationship,将不会导致本条记载被填充。
print(firstItemTagsCount)
/*
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZATTACHMENTCOUNT, t0.ZBIRTHOFYEAR, t0.ZTIMESTAMP FROM ZITEM t0
 CoreData: annotation: sql connection fetch time: 0.0003s
 CoreData: annotation: Bound intarray _Z_intarray0
 CoreData: annotation: Bound intarray values.
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZITEM FROM ZATTACHMENT t0 WHERE  t0.ZITEM IN (SELECT * FROM _Z_intarray0)  ORDER BY t0.ZITEM
 CoreData: annotation: sql connection fetch time: 0.0021s
 CoreData: annotation: total fetch execution time: 0.0024s for 1581 rows.
 CoreData: annotation: Prefetching with key 'attachments'.  Got 1581 rows.
 CoreData: annotation: total fetch execution time: 0.0053s for 190 rows.
 */

因为在 fetch 的过程中,经过 relationshipKeyPathsForPrefetching 中指定的联系数据的 NSManagedObjectID 已被一并提取。

五、运用对多联系的 count 设置谓词

对多联系的 count 也常常被用来作为谓词的条件运用。下面的代码将只回来 attachments(对多联系) count 大于 2 的成果。

let fetchquest = NSFetchRequest<Item>(entityName: "Item")
fetchquest.predicate = NSPredicate(format: "attachments.@count > 2")
let results = try? viewContext.fetch(fetchquest)
print(results?.count)
/*
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP FROM ZITEM t0 WHERE (SELECT COUNT(t1.Z_PK) FROM ZATTACHMENT t1 WHERE (t0.Z_PK = t1.ZITEM) ) > ?
 CoreData: annotation: sql connection fetch time: 0.0003s
 CoreData: annotation: total fetch execution time: 0.0006s for 144 rows.
 Optional(144)
 */

相似attachments.@count的办法只适用于谓词,无法将其作为排序条件。

六、经过派生特点记载对多联系的 count 数据

派生特点供给了对多联系 count 成果的预存才能。派生特点将在数据变化时(创立、更新、删除)依照设置,自动填充数据。在对 count 读取需求频繁的情况下,是极为优异的解决方案

在 Core Data 中查询和使用 count 的若干方法

完整的派生特点运用办法,请参阅 如何在 Core Data 中运用 Derived 和 Transient 特点。

七、运用派生特点记载的 count 进行排序

下面的代码中的 attachmentCount,是 Item 的派生特点,记载的是对多联系 attachments 的 count 数据。

let fetchquest = NSFetchRequest<Item>(entityName: "Item")
fetchquest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.attachmentCount, ascending: true)]
let items = (try? viewContext.fetch(fetchquest)) ?? []
print(items.count)
/*
 CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZATTACHMENTCOUNT, t0.ZBIRTHOFYEAR, t0.ZTIMESTAMP FROM ZITEM t0 ORDER BY t0.ZATTACHMENTCOUNT
 CoreData: annotation: sql connection fetch time: 0.0002s
 CoreData: annotation: total fetch execution time: 0.0004s for 190 rows.
 190
 */

在经过派生特点预存了 count 数据的情况下,完结办法四的需求将愈加简略。

八、运用 willSave 记载 count 数据

派生特点运用起来十分便利,但预置的办法有限。重写保管目标的 willSave 办法,能够取得更多的控制力。

比如下面的代码将只记载 attachment 中 title 长度大于 10 的 count 值

extension Item{
    public override func willSave() {
        super.willSave()
        let count = attachments?.allObjects.filter{
            (($0 as! Attachment).title?.count ?? 0 ) > 10
        }.count ?? 0
        setPrimitiveValue(Int32(count), forKey: "manualCount")
    }
}

在 willSave 中,咱们能够根据事务的需求对数据进行调整或记载。复杂的逻辑将对数据更改的功率产生一定的影响。

为现已上线运用的 CoreData 数据库增加派生特点或 willSave 办法时,需经过 mapping 或搬迁代码处理原有数据的新增特点。

九、查询某对多联系所有记载的 count 数据

当咱们想计算悉数记载(契合设定谓词)的某个对多联系的合计值时,在没有运用派生特点或 willSave 的情况下,能够运用下面的代码:

let fetchquest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
let expressDescription = NSExpressionDescription()
fetchquest.resultType = .dictionaryResultType
let name = "totalAttachment"
expressDescription.name = name
expressDescription.resultType = .integer32
let attachmentCount = NSExpression(format: "attachments")
let express = NSExpression(forFunction: "count:", arguments: [attachmentCount])
expressDescription.expression = express
fetchquest.propertiesToFetch = [expressDescription]
let result = (try? viewContext.fetch(fetchquest).first as? [String: Int32]) ?? [:]
print(result[name] ?? 0)
/*
 也能够直接从 Attachment 一侧进行查询
 CoreData: sql: SELECT COUNT( t1.Z_PK) FROM ZITEM t0 LEFT OUTER JOIN ZATTACHMENT t1 ON t0.Z_PK = t1.ZITEM
 CoreData: annotation: sql connection fetch time: 0.0002s
 CoreData: annotation: total fetch execution time: 0.0002s for 1 rows.
 Optional([{
     totalAttachment = 839;
 }])
 */

上述代码的要点描述:

  • 设置 resultType 为 dictionaryResultType
  • NSExpressionDescription 将被用在 propertiesToFetch 中,它的名称和成果将出现在回来字典中
  • NSExpression 在 Core Data 中运用的场景许多,例如在 Data Model Editor 中,许多的设定都是经过 NSExpression 完结的
  • 此办法中 NSExpression 运用的是 count 办法
  • 回来的成果是一个字典数组。需根据 propertiesToFetch,对字典的 Value 进行类型转换

运用此办法,SQLite 将在内部对 attachement 进行计数。

十、运用派生特点查询某对多联系所有记载的 count 数据

假如现已为对多联系设置了预存 count 的派生特点,能够运用下面的代码完结办法九的需求。

let fetchquest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
fetchquest.resultType = .dictionaryResultType
let expressDescription = NSExpressionDescription()
let name = "totalAttachment"
expressDescription.name = name
expressDescription.resultType = .integer32
let attachmentCount = NSExpression(format: "%K", #keyPath(Item.attachmentCount))
let express = NSExpression(forFunction: "sum:", arguments: [attachmentCount])
expressDescription.expression = express
fetchquest.propertiesToFetch = [expressDescription]
let result = (try? viewContext.fetch(fetchquest).first as? [String: Int32]) ?? [:]
print(result[name] ?? 0)
/*
 oreData: sql: SELECT total( t0.ZATTACHMENTCOUNT) FROM ZITEM t0
 CoreData: annotation: sql connection fetch time: 0.0001s
 CoreData: annotation: total fetch execution time: 0.0002s for 1 rows.
 1581
 速度快于上面的求和办法
 */

因为现已有了预存的 count 值,所以在 NSExpression 中运用的是 sum 办法。

相较于办法九,办法十的查询功率更高。

十一、查询分组后的 count 数据

某些场合下,咱们需求对数据进行分组,然后获取每组数据的 count。经过设置 propertiesToGroupBy,让 SQLite 为咱们完结这个作业。

例如,Item 有一个 birthOfYear 特点,该特点为年份数据( Int )。下面的代码,将数据依照 birthOfYear 进行分组,并回来每组的 count 数据:

let fetchquest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
fetchquest.propertiesToGroupBy = ["birthOfYear"]
fetchquest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.birthOfYear, ascending: false)]
fetchquest.resultType = .dictionaryResultType
let expressDescription = NSExpressionDescription()
expressDescription.resultType = .integer32
let name = "count"
expressDescription.name = name
let year = NSExpression(forKeyPath:\Item.birthOfYear)
let express = NSExpression(forFunction: "count:", arguments: [year])
expressDescription.expression = express
fetchquest.propertiesToFetch = ["birthOfYear",expressDescription]
let results = (try? viewContext.fetch(fetchquest) as? [[String:Int32]]) ?? []
print(results)
/*
 CoreData: sql: SELECT t0.ZBIRTHOFYEAR, COUNT( t0.ZBIRTHOFYEAR) FROM ZITEM t0 GROUP BY  t0.ZBIRTHOFYEAR
 CoreData: annotation: sql connection fetch time: 0.0002s
 CoreData: annotation: total fetch execution time: 0.0003s for 5 rows.
 [["birthOfYear": 2000, "count": 32], ["birthOfYear": 2001, "count": 36], ["count": 42, "birthOfYear": 2002], ["birthOfYear": 2003, "count": 44], ["birthOfYear": 2004, "count": 36]]
 */

因为此完结依靠的是 SQLite 的内部完结,因而将十分高效。

当事务逻辑中有相似的需求时,能够考虑为保管目标预设合适分组的特点。特点的内容也能够经过派生或 willSave 来处理。

十二、将分组后的 count 数据用作挑选条件

假如想对办法十一中获取的成果集进行挑选,除了经过代码操作成果数组外,运用 Core Data 对 having 的支持,直接在 SQLite 中进行将愈加的高效。下面的代码将只回来 count 大于 40 的成果。

let fetchquest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
fetchquest.propertiesToGroupBy = ["birthOfYear"]
fetchquest.resultType = .dictionaryResultType
let expressDescription = NSExpressionDescription()
expressDescription.resultType = .integer32
let name = "count"
expressDescription.name = name
let year = NSExpression(forKeyPath:\Item.birthOfYear)
let express = NSExpression(forFunction: "count:", arguments: [year])
expressDescription.expression = express
fetchquest.propertiesToFetch = ["birthOfYear",expressDescription]
// 创立变量
let countVariableExpr = NSExpression(forVariable: "count")
// 对 groupby 后的成果再度挑选
fetchquest.havingPredicate = NSPredicate(format: "%@ > 40",countVariableExpr)
let results = (try? viewContext.fetch(fetchquest) as? [[String:Int32]]) ?? []
print(results)
/*
 CoreData: sql: SELECT t0.ZBIRTHOFYEAR, COUNT( t0.ZBIRTHOFYEAR) AS __var0 FROM ZITEM t0 GROUP BY  t0.ZBIRTHOFYEAR HAVING __var0 > ?
 CoreData: annotation: sql connection fetch time: 0.0002s
 CoreData: annotation: total fetch execution time: 0.0002s for 2 rows.
 [["birthOfYear": 2002, "count": 42], ["birthOfYear": 2003, "count": 44]]
 */

因为成果会集的 count 并非保管目标的特点,无法直接将其运用在 NSPredicate 中。经过 NSExpression(forVariable: "count") 可解决该问题。

直接在 SQLite 中处理,功率将高于在代码中对办法十一的成果集数组进行操作。

总结

本文介绍的办法,无所谓孰优孰劣,每种办法都有其合适的场景。把握更多的基础知识、通盘考量,方可完结高效的解决方案。

期望本文能够对你有所帮助。

原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】