Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kenthumphries/438dabf757dfbb5ddae6 to your computer and use it in GitHub Desktop.
Save kenthumphries/438dabf757dfbb5ddae6 to your computer and use it in GitHub Desktop.
#import <CoreData/CoreData.h>
@implementation NSManagedObjectContext (FetchAggregates)
/**
* This method groups CoreData objects based on a list of user defined properties/relationships.
* Properties contain values, and Relationships are properties that specifically point to another NSManagedObject.
* Note: properties and relationships must be separated out as aggregate operations (count, max, min, avg) cannot be performed on relationships.
*
* NOTE: A common use for this method is finding duplicate NSManagedObject entities where 'duplicate' means exactly matching certain properties/relationships.
*
* This method finds all 'unique' groups of objects, where being unique means all objects in the group share the same values for the user defined properties/relationships.
* That is, if any two (or more) objects have the exact same value for each of the user defined properties/relationships
* (ie obj1.field1 == obj2.field1 && obj1.fieldN == obj2.fieldN), those two (or more) objects will be grouped together (obj1 & obj2 make one group).
*
* This method performs a count on the grouped objects and returns this count along with the property/relationship values that matched, making the group 'unique'.
* Example:
* Using a CarReservation entity, find any groups of CarReservations that have the same lastName for the same booked Car.
* This shows which Cars the user has decided to book more than once.
* Call the method with the input parameters:
* propertyNames = @["lastName"]
* relationshipNames = @["car"]
* entityName = @"CarReservation"
* The returned array could contain:
* @[ @{ "count"=2, "lastName"="Smith", "car"="<NSManagedObjectID:p1234>" },
* @{ "count"=1, "lastName"="Brown", "car"="<NSManagedObjectID:p4321>" },
* @{ "count"=1, "lastName"="Smith", "car"="<NSManagedObjectID:p4321>" },
* @{ "count"=1, "lastName"="Winehouse", "car"=nil } ]
*
* This can be interpreted as the user having:
* 2 reservations for car 'p1234' under name Smith (this car booked > once by Ms Smith)
* 1 reservation for car 'p4321' under name Smith
* 1 reservation for car 'p4321' under name Brown
* 1 reservation under name Winehouse, but no car (this is an invalid CarReservation object!)
*
*
* @param propertyNames NSArray of property name strings (any property of the entity EXCLUDING relationships) to group by. Must be non-empty.
* @param relationshipNames NSArray of relationship name strings (properties pointing to another NSManagedObject) to group by. Optional.
* @param entityName Name of entity for which to count aggregate properties
* @param context Context to fetch properties from
*
* @return NSArray of dictionaries containing the count and property values shared for each object group
*/
- (NSArray *)countGroupedByProperties:(NSArray *)propertyNames
groupedByRelationships:(NSArray *)relationshipNames
forEntity:(NSString *)entityName
{
NSAssert(entityName.length, @"Cannout count aggregate properties if no entityName passed in");
NSAssert(propertyNames.count, @"Cannot count aggregate properties if no propertyNames passed in");
if (!propertyNames.count || !entityName.length)
{
return nil;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
// Create the expression that gets the count of entities which are unique according to groupBy propertyNames
NSString *expressionString = [NSString stringWithFormat:@"%@:(%@)", @"count", propertyNames.firstObject];
NSExpression *countExpr = [NSExpression expressionWithFormat:expressionString];
NSExpressionDescription *countExprDesc = [[NSExpressionDescription alloc] init];
countExprDesc.name = @"count";
countExprDesc.expression = countExpr;
countExprDesc.expressionResultType = NSInteger64AttributeType;
// For each propertyName, get the corresponding NSAttributeDescription
NSMutableArray *desiredEntityProperties = [[NSMutableArray alloc] init];
NSDictionary *allEntityProperties = [self.persistentStoreCoordinator.managedObjectModel.entitiesByName[entityName] propertiesByName];
for (NSString *propertyName in propertyNames)
{
NSAttributeDescription *propertyAttribute = allEntityProperties[propertyName];
NSAssert(propertyAttribute, @"propertyName '%@' not found on entity '%@'. Returning nil", propertyName, entityName);
if (!propertyAttribute)
{
return nil;
}
[desiredEntityProperties addObject:propertyAttribute];
}
for (NSString *relationshipName in relationshipNames)
{
NSRelationshipDescription *relationshipAttribute = allEntityProperties[relationshipName];
NSAssert(relationshipAttribute, @"relationshipName '%@' not found on entity '%@'. Returning nil", relationshipName, entityName);
if (!relationshipAttribute)
{
return nil;
}
[desiredEntityProperties addObject:relationshipAttribute];
}
fetchRequest.propertiesToGroupBy = desiredEntityProperties;
fetchRequest.propertiesToFetch = [desiredEntityProperties arrayByAddingObject:countExprDesc];
fetchRequest.resultType = NSDictionaryResultType;
NSError *error;
NSArray *entityPropertyCounts = [self executeFetchRequest:fetchRequest error:&error];
return entityPropertyCounts;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment