Search code examples
ioscore-datanspredicateaggregationnsfetchrequest

iOS : How to fetch objects with maximum count in core data?


I've two classes in my core data model, and its one to many relationship.

Lets take Class Album <--->> Class Songs for the explanation. Now I want to fetch (top) five albums from Album which have the maximum no.of Songs.

Class Album
{
    albumID (Integer)
    name    (String)
    songs   (class B)
}

Class Songs
{
    name   (String)
    length (Integer)
}

@class Songs;
@interface Album : NSManagedObject

@property (nonatomic, retain) NSNumber * albumID;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSSet *songs;

@end

@interface Album (CoreDataGeneratedAccessors)
- (void)addSongsObject:(Songs *)value;
- (void)removeSongsObject:(Songs *)value;
- (void)addSongs:(NSSet *)values;
- (void)removeSongs:(NSSet *)values;
@end

@class Album;

@interface Songs : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * length;
@property (nonatomic, retain) Album *owner;

@end

Here's what I'm trying,

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Albums" inManagedObjectContext:[self context]]];
fetchRequest.fetchOffset = 0;
fetchRequest.fetchLimit = 5;
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"albumID" ascending:YES]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"SELF @[email protected]"];
NSError *error = nil;
NSArray *albums = [[self context] executeFetchRequest:fetchRequest error:&error];

If I don't pass predicate, it works fine. But problem is with predicate. It crash the app with following message:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "SELF @[email protected]"'

Update: I'm trying the same with NSExpression but not able to execute max: and count: functions together? below is what I've tried,

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Albums" inManagedObjectContext:[self context]]];

fetchRequest.fetchOffset = 0;
fetchRequest.fetchLimit = 5;

NSError *error = nil;

NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@"songs"];
NSExpression *countExpression = [NSExpression expressionForFunction:@"count:" arguments:[NSArray arrayWithObject:keyPathExpression]];
//NSExpression *maxExpression = [NSExpression expressionForFunction:@"max:" arguments:[NSArray arrayWithObject:keyPathExpression]];

NSExpressionDescription *maxCountExpressionDescription = [[NSExpressionDescription alloc] init];
[maxCountExpressionDescription setName:@"squishes"];
[maxCountExpressionDescription setExpression:countExpression];

[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:maxCountExpressionDescription]];
[fetchRequest setResultType:NSDictionaryResultType];

NSArray *albums = [[self context] executeFetchRequest:fetchRequest error:&error];

I think, I need to query like this,

SELECT album.name, max(count(album.songs)) FROM Album;

How to create such query in NSFetchRequest, is it possible with NSExpression or NSPredicate?


Solution

  • I've solved this in different way, I've added a songsCount property into Album class. I'm incrementing/decrement it each time when I add/remove a song from Album. Then I fetch all albums with sorting on songsCount in descending order.

    Once I can fetch all albums, I'll run a for loop and take out first five objects.

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Albums" inManagedObjectContext:[self context]]];
    fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"songsCount" ascending:NO]];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"songsCount > 0"];
    NSError *error = nil;
    NSArray *albums = [[self context] executeFetchRequest:fetchRequest error:&error];
    NSMutableArray *top5Albums = [NSMutableArray array];
    NSInteger totalCounts = [albums count];
    NSInteger maxIndex = (totalCounts >= 5) ? 5 : totalCounts;
    for(NSInteger index = 0; index < maxIndex; index++) {
        [top5Albums addObject:[albums objectAtIndex:index]];
    }