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
?
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]];
}