Jos*_*osh 4 core-data ios swift
给定一个已出版的图书表,其中有一date_published列 type NSAttributeType.DateAttributeType,我想知道每年出版了多少本书,如下所示:
Year | Books
-----+------
2013 | 76
2014 | 172
2015 | 155
Run Code Online (Sandbox Code Playgroud)
在普通的旧式 SQL 中,这很简单(尽管根据 RDBMS 略有不同):
SELECT DATEPART(yyyy, date_published) AS "Year", COUNT(*) AS "Books"
FROM books
GROUP BY DATEPART(yyyy, date_published)
Run Code Online (Sandbox Code Playgroud)
总的来说,我对 Swift 和 iOS 很陌生,但我看到的所有内容都建议要么预先计算年份并存储它,要么加载所有数据并自己计算。这些方法都不适合我,因为该年实际上是一个会计年度(存储后可能会有所不同)并且数据量可能很大。
大多数方法都是围绕向我的NSManagedObject. 这对我来说似乎已经太晚了,因为在这个阶段该对象还没有被加载到内存中。也有关于 的讨论NSFetchedResultsController,sectionNameKeyPath但是再次感觉在获取过程中为时已晚。我发现NSExpression很复杂,所以我很可能错过了一些东西,但似乎我无法在这里调用自定义 Swift 函数。真的,在一天结束时,我希望找到 DATEPART、DATEADD、DATEDIFF 等内置函数,并且我希望有人能为我指明正确的方向。
作为一个更具体的例子,考虑英国的纳税年度,即 4 月 6 日至 4 月 5 日。为了计算纳税年度,我会减去 3 个月零 6 天(到 4 月 5 日午夜)。因此,对于 2012 年 3 月 1 日出版的书,我会进行减法,得到 2011 年 11 月 24 日,其中包括闰年 2 月的 29 天。我只是从中提取年份部分 2011 年。因此 2012 年 3 月 1 日的英国纳税年度是 2011 年。我可以预先计算 2011 年并将其存储在新列中。但如果我从英国搬到澳大利亚,财政年度就会更改为七月到六月。我更有可能拥有一家会计期间与财政年度不同的公司(很可能在英国)。然后该公司被一家使用日历年的美国集团接管,每个人都很高兴,除了我的小应用程序认为 2012 年 3 月是 2011 年。
这是一些可以开始的样板......没有尝试按年份分组:
// The raw date for grouping by - no attempt to extract year
let date = NSExpressionDescription()
date.expression = NSExpression(format: "date_published")
date.name = "date"
date.expressionResultType = .DateAttributeType
// The number of books
let books = NSExpressionDescription()
books.expression = NSExpression(format: "count:(publication_title)")
books.name = "books"
books.expressionResultType = .Integer32AttributeType
// Put a fetch together
let fetch = NSFetchRequest(entityName:"Book")
fetch.resultType = .DictionaryResultType
fetch.propertiesToFetch = [date, books]
fetch.propertiesToGroupBy = [date]
// Execute now
var error: NSError?
if let results = context.executeFetchRequest(fetch,
error: &error) as Array<NSDictionary>? {
for row in results {
let date = row.valueForKey("date") as? NSDate
let books = row.valueForKey("books") as? Int
NSLog("%@ %d", date!, books!)
}
} else {
NSLog("Fail!")
}
Run Code Online (Sandbox Code Playgroud)
感谢您的指点!
正如您所发现的,这触及了 Core Data API 中的一个弱点。人们通常会解释说,人们不应该从 SQL 的角度来考虑 Core Data,因为它使用了一种不同的方法。日期是真正令人烦恼的地方,因为 Core Data 隐藏了一些 SQLite 功能。(他们这样做至少部分是因为 Core Data 不是 SQLite 包装器,并且可以与其他非 SQL 存储系统一起使用)。
核心问题是 Core Data 的“Date”类型对应于NSDate,而NSDate又只是一个浮点数,表示自参考日期以来的秒数。它不包括年、月或日。这些值甚至不是固定的,因为例如,由 表示的时间NSDate可能意味着加利福尼亚州的日期与日本不同。不幸的是,这些类型名称中的“日期”一词具有误导性。
这就是为什么人们通常建议使用额外的字段,或者至少使用不同的数据类型,对于使用核心数据的应用程序,这些应用程序需要考虑某个时区的实际日期,而不是无论区域如何的精确时间点。没有一种好方法可以构建对“日期”字段进行操作的核心数据查询来满足您的需要。处理这个问题归根结底是存储您实际需要的数据,而不是存储近似您需要的数据——除了将此类型称为“日期”会混淆选择。您不需要此处使用核心数据“日期”类型。
因此,让我们考虑一种方法来获得所需的结果,同时让 SQLite 完成尽可能多的工作。假设您将日期字段替换为integerDate使用以下格式将日期表示为整数(核心数据“Integer 64”)的字段yyyyMMDD。今天将存储为 20151223。理论上,这可以通过一些NSExpression魔法一步完成,但 Core Data 不允许您按表达式进行分组,所以这是不可能的。
第 1 步:获取所有不同的年份值
NSExpression *yearExpression = [NSExpression expressionWithFormat:@"divide:by:(%K,10000)", @"integerDate"];
NSExpressionDescription *yearExpDescription = [[NSExpressionDescription alloc] init];
yearExpDescription.name = @"year";
yearExpDescription.expression = yearExpression;
yearExpDescription.expressionResultType = NSInteger64AttributeType;
NSFetchRequest *distinctYearsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Event"];
distinctYearsRequest.resultType = NSDictionaryResultType;
distinctYearsRequest.returnsDistinctResults = YES;
distinctYearsRequest.propertiesToFetch = @[ yearExpDescription ];
NSError *fetchError = nil;
NSArray *distinctYearsResult = [self.managedObjectContext executeFetchRequest:distinctYearsRequest error:&fetchError];
if (distinctYearsResult != nil) {
NSLog(@"Results by year: %@", distinctYearsResult);
}
NSArray *distinctYears = [distinctYearsResult valueForKey:@"year"];
Run Code Online (Sandbox Code Playgroud)
上式中,通过简单除法yearExpression得到年份部分integerDate。当上面完成时,distinctYears包含 代表的所有年份integerDate。
第 2 步:循环遍历年份,获取每个年份的计数:
NSMutableDictionary *countByYear = [NSMutableDictionary dictionary];
for (NSNumber *year in distinctYears) {
NSFetchRequest *countForYearFetch = [NSFetchRequest fetchRequestWithEntityName:@"Event"];
countForYearFetch.resultType = NSDictionaryResultType;
countForYearFetch.propertiesToFetch = @[ yearExpDescription ];
NSExpression *targetYearExpression = [NSExpression expressionForConstantValue:year];
NSPredicate *yearPredicate = [NSComparisonPredicate predicateWithLeftExpression:yearExpression rightExpression:targetYearExpression modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
countForYearFetch.predicate = yearPredicate;
NSError *fetchError = nil;
NSUInteger countForYear = [self.managedObjectContext countForFetchRequest:countForYearFetch error:&fetchError];
countByYear[year] = @(countForYear);
}
NSLog(@"Results by year: %@", countByYear);
Run Code Online (Sandbox Code Playgroud)
这会每年进行一次单独的提取,但通过仅提取结果计数而不是实际数据来保持较低的内存开销。完成后,countByYear将根据字段显示按年份划分的条目数integerDate。
说了这么多,请记住,您确实可以选择直接使用 SQLite,而不是使用 Core Data。PLDatabase将为您提供 Objective-C 风格的包装器,同时仍然允许对 SQLite 可以执行的所有操作进行原始 SQL 查询。
| 归档时间: |
|
| 查看次数: |
500 次 |
| 最近记录: |