Kevin Hakanson
Kevin Hakanson

Reputation: 42240

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;

Upvotes: 3

Views: 5423

Answers (2)

Fourj
Fourj

Reputation: 1917

You may try this: Get songs by unique artist name with play count more than 0.

  NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MYSong"];
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"plays.@count > 0"];
  [request setPredicate:predicate];
  NSEntityDescription *entity = [NSEntityDescription entityForName:@"MYSong" inManagedObjectContext:context];
  NSDictionary *entityProperties = [entity propertiesByName];
  [request setReturnsDistinctResults:YES];
  [request setPropertiesToFetch:[NSArray arrayWithObject:[entityProperties objectForKey:@"artist"]]];

  NSError *error = nil;
  NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];

See more for unique data result fetching: Retrieving a unique result set with Core Data

Upvotes: 1

rjohnson
rjohnson

Reputation: 76

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);

Upvotes: 4

Related Questions