46

I am fetching a set of objects from a Core Data persistent store using a fetch request and a predicate. My current predicate simply checks whether an attribute is >= a certain value. This all works great, except that I want to finally exclude any objects that are currently held in an array.

I basically need to be able to exclude a set of objects, and the only way I think I can do this is to be able to get a list of objectID from my managed objects array, and create another expression in my predicate to ensure that any objects returned don't have the same objectID. I.E.@"ANY records.objectID NOT IN %@", arrayOfObjectID.

How can I do this?

rptwsthi
  • 10,094
  • 10
  • 68
  • 109
Michael Waterfall
  • 20,497
  • 27
  • 111
  • 168

3 Answers3

82

A predicate like

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (self IN %@)", arrayOfExcludedObjects];

where the entity of the fetch request is the entity of objects in the array, should do what you want. This can, of course be combined with other clauses in a single predicate for a fetch request.

In general, object comparisons (e.g self == %@ or self IN %@) compare on objectID in Core Data queries. The argument can either be an NSManagedObject instance or an NSMangedObjectID instance. So the predicate format above could take arrayOfExcludedObjects or [arrayOfExcludedObjects valueForKey:@"objectID"] as the argument.

Barry Wark
  • 107,306
  • 24
  • 181
  • 206
  • 1
    Thanks for your help. I tried using [NSPredicate predicateWithFormat:@"self NOT IN %@", myArrayOfManagedObjects] but I kept getting an 'Unable to parse the format string "self NOT IN %@" error at runtime. Any ideas? – Michael Waterfall Jun 04 '09 at 15:41
  • Sorry.. my mistake for not testing before I posted. I've corrected my answer (use "NOT (self IN %@)" instead of "self NOT IN %@"). – Barry Wark Jun 04 '09 at 18:21
  • This worked well for me. I used it to filter down using complex logic that is not supported in SQL. But predicateWithBlock does not work (for reasonable reasons) on SQL. – Tom Andersen Oct 06 '16 at 20:42
10

While @BarryWark's answer is correct when working with fetch requests, I want to write a warning to the folks who will try to apply this rule to a filtering of Core Data to-many relationships.

Shortly: If when filtering to-many relationships you use a predicate and your array of objects for IN query is an array of objectIDs - then you should use self.objectID in your query string like

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(self.objectID IN %@)", arrayOfObjectIDs];

Because using just (self IN %@) in the case of filtering to-many relationships will result in incorrect results - it is just an NSArray that evaluates predicates and it knows nothing about Core Data's NSManagedObjectID stuff.

I've crafted special test code showing this. Sorry for so many lines, but they worth it. There are two entities: User and Post and User has a to-many relationship named "posts".

User *user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([User class]) inManagedObjectContext:managedObjectContext()];

Post *post = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Post class]) inManagedObjectContext:managedObjectContext()];

[user addPostsObject:post];

[managedObjectContext() save:nil];

// 1. Both filtered relationship array and fetch result are correct!
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(self IN %@)", @[ post ]];

NSSet *filteredRelationship = [user.posts filteredSetUsingPredicate:predicate];

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Post"];
NSArray *fetchResult = [managedObjectContext() executeFetchRequest:fetchRequest error:nil];

NSLog(@"\n\n\nPredicate: %@", predicate);
NSLog(@"filteredRelationship: %@", filteredRelationship);
NSLog(@"fetchResult: %@", fetchResult);

// 2. Filtered relationship array is empty (wrong), fetch result is correct, !
predicate = [NSPredicate predicateWithFormat:@"(self IN %@)", @[ post.objectID ]];

filteredRelationship = [user.posts filteredSetUsingPredicate:predicate];

fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Post"];
fetchResult = [managedObjectContext() executeFetchRequest:fetchRequest error:nil];

NSLog(@"\n\n\nPredicate: %@", predicate);
NSLog(@"filteredRelationship: %@", filteredRelationship);
NSLog(@"fetchResult: %@", fetchResult);

// 3. Filtered relationship array is empty (wrong), fetch result is correct
predicate = [NSPredicate predicateWithFormat:@"(self.objectID IN %@)", @[ post ]];

filteredRelationship = [user.posts filteredSetUsingPredicate:predicate];

fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Post"];
fetchResult = [managedObjectContext() executeFetchRequest:fetchRequest error:nil];

NSLog(@"\n\n\nPredicate: %@", predicate);
NSLog(@"filteredRelationship: %@", filteredRelationship);
NSLog(@"fetchResult: %@", fetchResult);

// 4. Filtered relationship array is correct, fetch result is correct
predicate = [NSPredicate predicateWithFormat:@"(self.objectID IN %@)", @[ post.objectID ]];

filteredRelationship = [user.posts filteredSetUsingPredicate:predicate];

fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Post"];
fetchResult = [managedObjectContext() executeFetchRequest:fetchRequest error:nil];

NSLog(@"\n\n\nPredicate: %@", predicate);
NSLog(@"filteredRelationship: %@", filteredRelationship);
NSLog(@"fetchResult: %@", fetchResult);

TLDR output

<redacted> Predicate: SELF IN {<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: { content = nil; title = nil; user = "0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>"; })}

<redacted> filteredRelationship: {(<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: { content = nil; title = nil; user = "0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>"; }) )}

<redacted> fetchResult: ("<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: {\n    content = nil;\n    title = nil;\n    user = \"0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>\";\n})")

<redacted> Predicate: SELF IN {0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1>}

<redacted> filteredRelationship: {()}

<redacted> fetchResult: ("<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: {\n    content = nil;\n    title = nil;\n    user = \"0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>\";\n})")

<redacted> Predicate: objectID IN {<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: { content = nil; title = nil; user = "0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>";})}

<redacted> filteredRelationship: {()}

<redacted> fetchResult: ("<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: {\n    content = nil;\n    title = nil;\n    user = \"0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>\";\n})")

<redacted> Predicate: objectID IN {0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1>} 

<redacted> filteredRelationship: {(<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: { content = nil; title = nil; user = "0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>";}))}

<redacted> fetchResult: ("<Post: 0x2a04f10> (entity: Post; id: 0x2a56c40 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/Post/p1> ; data: {\n    content = nil;\n    title = nil;\n    user = \"0x2af2a20 <x-coredata://9D07BF41-2DC0-42C1-9DD8-6082A00E7BEB/User/p1>\";\n})")
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
  • It's not about relationships, it's about the difference of using predicate in NSFetchRequest and using predicate in NSSet API filteredSetUsingPredicate. "self.objectID" is illegal in NSFetchRequest. Your code is wrong, you've missed the line `fetchRequest.predicate = predicate;` – Cosyn Jan 26 '22 at 16:02
5

Swift 3 solution

I faced the same situation therefore I m posting a swift 3 solution below

let predicate = NSPredicate(format:"NOT (self IN %@)",[arrayofNSManagedObjects])
Rajat Jain
  • 1,002
  • 1
  • 15
  • 24