Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using NSFetchRequest from Core Data to get aggregated relationship counts?

I am having trouble understanding how to best use Core Data to solve this problem, including the right terminology to describe the problem. Below is a illustrative sample of the problem (but not my actual objects). Assume you have a music playing system, where artists have songs, and every time a song is played in the system, the time stamp is recorded.

Question: How can I find the count of artists with songs played?

Here are sample NSManagedObject

@interface MYArtist : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSSet *songs;
@end

@interface MYSong : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) MYArtist *artist;
@property (nonatomic, retain) NSSet *plays;
@end

@interface MYPlay : NSManagedObject
@property (nonatomic, retain) NSDate *playDate;
@property (nonatomic, retain) MYSong *song;
@end

Below is sample data, for Artists, Songs and Plays, include the play date:

A1 <-+-> S1_A1 <---> P1_S1_A1 (2012-08-31) 
     '-> S2_A1 <-+-> P1_S2_A1 (2012-08-31) 
                 '-> P2_S2_A1 (2012-09-01)
A2 <-+-> S1_A2
     '-> S2_A2 <---> P1_S2_A2 (2012-08-31) 
A3 <---> S1_A3 

Using the code below, I can fetch all the MYPlay objects and build a set of artists and then find the size of the set at the end. The resulting set from this sample data and code would be [A1, A2] with count = 2. However, I would expect some NSPredicate syntax or use of countForFetchRequest to be more efficient, instead of iterating over the objects and faulting them into memory.

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"MYPlay"];
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

NSMutableSet *set = [[NSMutableSet alloc] init];
for (id result in results) {
    MYPlay *play = (MYPlay *)result;
    [set addObject:play.song.artist];
}

NSUInteger count = set.count;
like image 691
Kevin Hakanson Avatar asked Jan 29 '26 15:01

Kevin Hakanson


1 Answers

The poorly documented SUBQUERY might be a good solution, and you can nest them so you can check associations of associations.

So:

NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"MYArtist"]; 
request.predicate = [NSPredicate predicateWithFormat:@"(SUBQUERY(songs,$song,SUBQUERY($song.plays,$play,$play.playDate NOT NULL).@count > 0).@count >0)"]; // I'm assuming that songs with out a play have a nil playDate

NSUInteger count = [context countForFetchRequest:request error:&error];

I haven't tested that subquery string, but that should get you started. There's a few good resources out there if you google for them, but not much in the Apple docs. Documentation for NSExpression states the "string format for a subquery expression is:"

SUBQUERY(collection_expression, variable_expression, predicate);

like image 105
rjohnson Avatar answered Feb 01 '26 06:02

rjohnson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!