2

I'm trying to get the average value of an attribute in a child entity while also trying to only include a select set of records.

I have two entities in my Core Data model: Invoice and InvoiceDetail.

Invoice:<br>
  invoiceNum - attribute<br>
  invoiceDate - attribute<br>
  invoiceDetails - one-to-many relationship to InvoiceDetail

InvoiceDetail:<br>
  itemAmount - attribute<br>
  itemType - attribute<br>
  invoice - one-to-one relationship to Invoice<br>

If I wanted to just get the average value of itemAmount for an entire invoice, I would use the following (invoice is an NSManagedObject):

float avgAmount = [[invoice valueForKeyPath:@"invoiceDetails.@avg.itemAmount"] floatValue];

However, I'm trying to only get the average for objects where itemType = 1. I can loop through the invoiceDetail items and do this manually, but I know that this will cause a performance issue. I'm not sure what is the best way to go about doing this.

Thanks for your help.

PengOne
  • 48,188
  • 17
  • 130
  • 149
user684360
  • 47
  • 1
  • 6

1 Answers1

8

You can do it with a fetch request that contains an expression, as follows:

- (NSDictionary *)myFetchResults
{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    request.entity = [NSEntityDescription entityForName:@"InvoiceDetail" inManagedObjectContext:myContext];
    request.predicate = [NSPredicate predicateWithFormat:@"itemType = %@", [NSNumber numberWithInt:1]];

    request.resultType = NSDictionaryResultType;

    NSExpressionDescription *aveExDescr = [[NSExpressionDescription alloc] init];
    [aveExDescr setName:@"myAverage"];
    [aveExDescr setExpression:[NSExpression expressionForFunction:@"average:" 
                                                        arguments:[NSArray arrayWithObject:
                                                                   [NSExpression expressionForKeyPath:@"itemAmount"]]]];
    [aveExDescr setExpressionResultType:NSFloatAttributeType];

    request.propertiesToFetch = [NSArray arrayWithObject:aveExDescr];

    NSError *err = nil;
    NSArray *results = [self.moContext executeFetchRequest:request error:&err];
    [request release];
    [err release];

    return results;
}

The fetch will return a dictionary, which you can access as follows:

NSArray *results = [self myFetchResults];
NSDictionary *resultsDictionary = [results lastObject];
NSNumber *average = [resultsDictionary objectForKey:@"myAverage"]; 

Note that this code hasn't been tested. You might also use NSDecimalAttributeType instead of the float type if you're working with NSDecimalNumbers.

salo.dm
  • 2,317
  • 1
  • 15
  • 16
  • Thank you very much, this is really helpful! I was just asked for another filter that I am struggling to include. The Invoice entity contains another attribute called invoiceStatus with integer values. If I wanted to also filter for Invoice objects where the invoiceStatus was 1, for example, can the NSPredicate work with multiple entities? I was trying to change the FetchRequest entity to "Invoice" and use a predicate of: @"invoiceStatus == %@ AND invoiceDetails.itemType == %@", but that isn't working. – user684360 Nov 15 '11 at 22:59
  • Yes, you can work with multiple entities and also compound predicates. Your predicate isn't working because you're operating improperly on a to-many relationship. Invoice has many InvoiceDetails, and the predicate should be asking whether there is at least one that has an itemType with the requested value. You can do that with a subquery. – salo.dm Nov 15 '11 at 23:15
  • These questions ([A](http://stackoverflow.com/questions/2029962/using-nspredicate-with-core-data-for-deep-relationships) & [B](http://stackoverflow.com/questions/2006927/whats-better-way-to-build-nspredicate-with-to-many-deep-relationships)) have some examples of how to set up a subquery. – salo.dm Nov 15 '11 at 23:15
  • Note that if you're still getting the average of itemAmount, you may have to change the fetch request entity back to InvoiceDetails. I don't think you can build an expression with a to-many relationship, though I may be wrong. Anyway, the predicate will be easier to compose and you won't need the subquery. It would be very similar to the predicate you showed in your comment. – salo.dm Nov 15 '11 at 23:43