Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Core Data Aggregate Functions

Core Data is a beautifully designed object mapping and persistence framework — big kudos to the Apple engineers.

With Core Data, it is really easy to retrieve and store objects. However, I ran into the need to retrieve the maximum value for a single attribute.

If I was using SQL, I would just use the MAX() aggregate function to determine the maximum value for the column in my data set. Not having the ability to directly query Core Data, I was trying to figure out how I could easily perform an aggregate function on my data.

My first approach, which was mistakenly incorrect, was to retrieve the object from Core Data with the the maximum value using a simple NSPredicate. After coding this up, I knew there had to be a better way.

I didn’t need to retrieve the entire object, just the single value. Sure it was running pretty fast on my simulator, but I knew that there was some overhead built into my solution. I was creating an object that I didn’t even want or need. After some reading, I found the NSExpressionDescription class. This was exactly what I needed.

Getting Started with NSExpressions

To use the NSExpressionDescription class, we need to first create an expression to tell our NSFetchRequest which attribute (column) and which calculation (aggregation) we want to perform. In my case, I am going to be looking for the maximum wordID stored in my Word entity.

//create the NSExpression to tell our NSExpressionDescription which attribute we are performing the calculation on
NSExpression *keyExpression = [NSExpression expressionForKeyPath:@"wordID"];

//create the NSExpression to tell our NSExpressionDescription which calculation we are performing.
NSExpression *maxExpression = [NSExpression expressionForFunction:@"max:" arguments:[NSArray arrayWithObject:keyExpression]];

I am using the max: function in this example. There are other functions that are supported by NSExpression:

These are not all of the predefined functions, but some of the most commonly used ones. You can read more about the expressionForFunction:arguments: method, the predefined functions, and the arguments that are necessary for each function. In my example, we are using the max: function, which only takes a single argument, the expression containing the key path.

Building the NSExpressionDescription

Next, we will create a new NSExpressionDescription and specify the resulting name, the resulting data type, and the expression we defined above.

NSExpressionDescription *description = [[NSExpressionDescription alloc] init];
[description setName:@"maxWordID"];
[description setExpression:maxExpression];
[description setExpressionResultType:NSInteger32AttributeType];

On the first line above we allocated and initialized the new instance of the NSExpressionDescription class. We can then set the name, expression and result type properties. The name is the attribute key that will be returned when we perform the fetch request on the context. The expression is the instance of NSExpression that we created previously. Lastly, we specify the NSAttributeTypefor storing the resulting value. In this case I am using the NSInteger32AttributeType because I am expecting a signed integer.

Finally, we need to tell our instance of NSFetchRequest that we only want to fetch the single property. To do this, we call setPropertiesToFetch: and specify the array of properties as the single NSExpressionDescription instance:

[request setPropertiesToFetch:[NSArray arrayWithObject:description]];

Performing the NSFetchRequest

After setting everything up, we then execute the fetch, which will result in a single object returned with the value of our aggregate function stored in the key we specified (above where we called setName:).

NSArray *results = [context executeFetchRequest:request error:&error];
if (results != nil && results.count > 0){
  NSNumber *maxWordID = [[results objectAtIndex:0] valueForKey:@"maxWordID"];
  NSLog(@"The maximum Word.wordID is currently: %@", maxWordID);
}

You can read more about fetching specific objects in Apple’s Core data Programming Guide.