gpt4 book ai didi

ios - 相当于 SQL DATEPART 的核心数据

转载 作者:搜寻专家 更新时间:2023-10-31 22:21:34 31 4
gpt4 key购买 nike

给定一个出版的书籍表,其中有一个 date_published 列,类型为 NSAttributeType.DateAttributeType,我想知道每年出版了多少本书:

Year | Books
-----+------
2013 | 76
2014 | 172
2015 | 155

在普通的旧 SQL 中,这很简单(尽管它因 RDBMS 而略有不同):

SELECT DATEPART(yyyy, date_published) AS "Year", COUNT(*) AS "Books"
FROM books
GROUP BY DATEPART(yyyy, date_published)

总的来说,我是 Swift 和 iOS 的新手,但我看到的所有内容都建议要么预先计算年份并存储它,要么加载所有数据并自己计算。这些方法都不适合我,因为年份实际上是一个会计年度(存储后可能会有所不同)并且数据量可能很大。

大多数方法都围绕着向我的 NSManagedObject 添加自定义属性。这对我来说似乎为时已晚,因为在这个阶段该对象不会被加载到内存中。也有关于 NSFetchedResultsControllersectionNameKeyPath 的讨论,但再次感觉在获取过程中为时已晚。我发现 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 年并将其存储在新列中。但如果我从英国搬到澳大利亚,财政年度就会改为 7 月到 6 月。我更有可能拥有一家会计期间与会计年度不同的公司(很可能在英国)。那家公司随后被一个使用日历年的美国集团接管,每个人都很高兴,除了我的小应用程序认为 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!")

}

谢谢指点!

最佳答案

正如您所发现的,这触及了 Core Data API 中的一个弱点。人们通常会解释说,不应从 SQL 的角度来考虑 Core Data,因为它使用了不同的方法。日期是非常烦人的地方,因为 Core Data 隐藏了一些 SQLite 功能。 (他们这样做至少部分是因为 Core Data 不是 SQLite 包装器,并且可以与其他非 SQL 存储系统一起使用)。

核心问题是Core Data的“Date”类型对应的是一个NSDate,而NSDate又只是一个 float ,表示从那以后的秒数引用日期。它不包括年、月或日。这些值甚至都不是固定的,因为 NSDate 表示的即时时间在加利福尼亚可能意味着与日本不同的日期。不幸的是,这些类型名称中的“日期”一词具有误导性。

这就是为什么人们通常建议为使用 Core Data 的应用程序使用额外的字段或至少不同的数据类型,这些应用程序需要考虑某个时区的实际日期,而不是不考虑时区的精确时间。没有一个好的方法来构建一个核心数据查询,该查询在“日期”字段上运行,满足您的需要。解决这个问题归结为存储您实际需要的数据,而不是存储您实际需要的数据——除了将这种类型称为“日期”会使选择混淆。您不需要在此处输入核心数据“日期”。

因此,让我们考虑一种方法来获得所需的结果,同时让 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"];

在上面,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);

这会为每年单独获取一次,但通过只获取结果计数而不是实际数据来保持较低的内存开销。完成后,countByYear 根据 integerDate 字段获得按年份的条目数。

说了这么多,请记住,您确实可以选择直接使用 SQLite 而不是使用 Core Data。 PLDatabase将为您提供一个 Objective-C 风格的包装器,同时仍然允许对 SQLite 可以做的所有事情进行原始 SQL 查询。

关于ios - 相当于 SQL DATEPART 的核心数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34439038/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com