Skyler
Skyler

Reputation: 2864

Core Data - NSPredicate for objects with identical column and also meet another condition

I am looking for a predicate to fetch all managed objects of type Entity whose values are duplicated in a property sessionId, where all groups' ("groups", meaning managed objects whose sessionId's are equal) contents' flags in a property processed is set to YES. This can be done (slowly), but I am looking for an efficient one liner for this. Thanks

This is the slow way:

NSFetchRequest *request = [NSEntityDescription entityForName:@"Entity"
                                      inManagedObjectContext:context];
NSArray *all = [context executeFetchRequest:request error:nil];
NSArray *sessionIds = [all valueForKeyPath:@"@distinctUnionOfObjects.sessionId"];
NSMutableArray *objects = [NSMutableArray array];
for (NSString *sessionId in sessionIds) {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"sessionId == %@", sessionId];
    NSArray *inSession = [all filteredArrayUsingPredicate:predicate];
    for(id obj in inSession) {
         if(![obj valueForKey:@"processed"]) continue;
    }

    [objects arrayByAddingObjectsFromArray:processed];
}
NSLog(@"%@", objects);

Upvotes: 0

Views: 189

Answers (1)

pbasdf
pbasdf

Reputation: 21536

Since booleans are stored as 0s and 1s, a group where all rows have processed = YES will have average(processed) = 1. Hence you can use NSFetchRequest's propertiesToGroupBy and havingPredicate to get the sessionIds that meet your criteria. A second fetch is then required to get the Entity objects with any of those sessionIds:

NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = @[@"sessionId"];
fetch.propertiesToGroupBy = @[@"sessionId"];
fetch.havingPredicate = [NSPredicate predicateWithFormat: @"average:(processed) == 1"];
NSArray *resultsArray = [context executeFetchRequest:fetch error:nil];
NSArray *sessionIdArray = [resultsArray valueForKeyPath:@"sessionId"];
NSFetchRequest *newFetch = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
newFetch.predicate = [NSPredicate predicateWithFormat:@"name IN %@",sessionIdArray];
NSArray *finalResults = [context executeFetchRequest:newFetch error:nil];
NSLog(@"Final results, %@", finalResults);

Sorry it's not a one-liner. And I leave it to you to determine whether it's any quicker than your own code.

EDIT

To do it all in one fetch, use NSFetchRequestExpression in place of the intermediate arrays:

NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = @[@"sessionId"];
fetch.propertiesToGroupBy = @[@"sessionId"];
fetch.havingPredicate = [NSPredicate predicateWithFormat: @"average:(processed) == 1"];
NSExpression *fetchExpression = [NSFetchRequestExpression expressionForFetch:[NSExpression expressionForConstantValue:fetch] context:[NSExpression expressionForConstantValue:context] countOnly:false];
NSFetchRequest *newFetch = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
newFetch.predicate = [NSPredicate predicateWithFormat:@"sessionId IN %@",fetchExpression];
NSArray *finalResults = [context executeFetchRequest:newFetch error:nil];
NSLog(@"Final results, %@", finalResults);

Note that on my (admittedly trivial) test setup this actually ran more slowly than the two-fetch solution.

FYI, if you use the SQLDebug build setting to examine the SQL that is generated, it looks something like this:

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZSESSIONID, t0.ZPROCESSED FROM ZENTITY t0 WHERE t0.ZSESSIONID IN (SELECT n1_t0.ZSESSIONID FROM ZENTITY n1_t0 GROUP BY n1_t0.ZSESSIONID HAVING avg( n1_t0.ZPROCESSED) = ? )

Upvotes: 1

Related Questions