【问题标题】:Help with CoreData QueryCoreData 查询帮助
【发布时间】:2011-01-20 06:52:08
【问题描述】:

我正在寻找在 CoreData 中编写一些基本查询的方法,但文档中没有示例。以下是我的查询:

  1. 我有一个费用对象,它有一个费用金额字段。
  2. 费用可以链接到 ExpenseCategory 对象。
  3. ExpenseCategory 可以只定义费用的类别(例如食品),也可以具有单位费率(例如里程)。如果只是名称,则费用值为 Expense 对象中的费用金额,否则为 ExpenseCategory 中的费用金额 * unitRate。
  4. 由于类别链接的费用是可选的,因此最终费用将基于类别的存在/不存在和单位费率。

所以计算总费用的 SQL 查询将是:

select
    TOTAL(e.amount * IFNULL(c.rate, 1))
from EXPENSE e
LEFT OUTER join CATEGORY c on
    e.category = c.id

如何在 CoreData 中做到这一点?

【问题讨论】:

  • 感谢您提出这个问题。我有一个类似的问题,NSExpression 对我不起作用——我正在使用双精度和时间戳,出于某种原因,divide:by: 函数会产生奇怪的结果。我对您的最终解决方案很好奇,因为我还需要在计算中包含多条记录。

标签: iphone core-data


【解决方案1】:

一个更简单的解决方案可能是在您的 Expense 类上实现另一个方法,该方法为您提供适当计算的金额。

例如

- (NSDecimalNumber *) calculatedExpenseAmount {

  NSDecimalNumber *actualAmount = self.expenseAmount;

  // Pseudo-code begins
  if (self.expenseCategory != nil) {
    actualAmount = self.expenseAmount * self.expenseCategory.unitRate;
  }

  return actualAmount;
}

我正在补充我之前的答案。

如果您想避免拉入每个托管对象,您可以使用 NSDictionary-result 查询来拉出费用数量和费用类别.unitRate 值。

- (NSDecimalNumber *) totalExpenses
{
    // Fetch all of the expense amounts and unit rate of any related category.

    NSFetchRequest *request = ...;
    [request setManagedObjectContext:<...>];
    [request setEntity:<ExpenseAccountDescription>];
    [request setResultType:NSDictionaryResultType];
    NSArray *props = [NSArray arrayWithObjects:@"expenseAmount", @"category.unitRate", nil];
    [request setPropertiesToFetch:props];

    NSArray *amounts = [request executeRequest:...];
    // amounts is an array of dictionaries, each hold the desired property values.

    // Loop and sum the individual amounts

    NSDecimal *total = [[NSDecimalNumber zero] decimalNumber];
    NSAutoreleasePool *pool = nil; // contain the mess

    NSCalculationError err = NSCalculationNoError;

    for (NSDictionary *result in amounts) 
    {
        pool = [NSAutoreleasePool new];

        NSDecimal newTotal = [[NSDecimalNumber zero] decimalNumber];
        NSDecimalNumber *expenseAmount = [result valueForKeyPath:@"expenseAmount"];
        NSDecimalNumber *unitRate = [result valueForKeyPath:@"category.unitRate"];

        if (unitRate != nil) {
            // do the unit rate multiplication and accumulate the result in the total

            NSDecimal calculated = [[NSDecimalNumber zero] decimalNumber];
            err = NSDecimalMultiply (&calculated, [expenseAmount decimalNumber], [unitRate decimalNumber], NSRoundBankers);
            if (err == NSCalculationNoError) {
                err = NSDecimalAdd (&newTotal, total, calculated, NSRoundBankers);
            }
        }
        else {
            // just accumulate the result in the total

            err = NSDecimalAdd (&newTotal, total, [expenseAmount decimalNumber], NSRoundBankers);
        }

        // Keep the new total
        NSDecimalCopy(&total, newTotal);

        [pool drain];
    }

    return [NSDecimalNumber decimalNumberWithDecimal:total];
}

如果您有 10000 个费用条目,则此提取和计算可能占用不到 1MB 的 RAM。仪器将是您测量它的朋友。

【讨论】:

  • 这只需一笔费用。我正在尝试整理数据库中的所有费用,而不是在内存中读取它们。
  • 感谢您的帮助。我尝试使用来自 NSExpression 类的 multiply:by: 在 sqlite 本身中进行此计算。模拟器引发了 RT 异常,我认为它在 iPhone 上尚不可用。我在这里无法理解的是为什么 CoreData 不支持这么简单的事情。是的,我知道 CD 是从对象图的角度思考的,最终都是关于数据的。
  • 我很确定没有办法让 CD 执行任意 SQL。如果你真的很坚持,你可以尝试直接进入 SQLite。它支持你想做的所有事情。