I'm working on an iphone application and I have a simple many-to-many relationship set up with Group and Contact objects. A group can have many contacts and contacts can belong to multiple groups.
I'm trying to select all groups that a particular contact does NOT already belong to using the following predicate. (Note: the uid field is a string field that I used to uniquely identify contact entities)
[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId]
According to Apple's Predicate Programming Guide, the ALL aggregate operation is valid but I get the following exception indicating that this is an unsupported predicate:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported predicate (null)'
I can use a similar predicate to select all groups that a contact does already belong to using this predicate so it appears that I have all of the relationships and fields defined properly.
[NSPredicate predicateWithFormat:@"ANY contacts.uid == %@", contactUId]
The exception is thrown when constructing the predicate and not when I'm trying to actually execute the fetch request so it seems to be related to the syntax I'm using rather than Core Data support. What am I doing wrong?
The Core Data Programming Guide says
There are some interactions between fetching and the type of store. In the XML, binary, and in-memory stores, evaluation of the predicate and sort descriptors is performed in Objective-C with access to all Cocoa's functionality, including the comparison methods on NSString. The SQL store, on the other hand, compiles the predicate and sort descriptors to SQL and evaluates the result in the database itself.
It goes on to describe some other limitations on the use of NSPredicate with NSSQLiteStoreType, but your use of "ALL" here is an (undocumented) limitation that has to do with how the fetch request emits SQL.
Under the hood, CoreData generates three tables for your schema:
And so when you call myGroup.contacts, something like this gets run:
select * from Group join JOIN_TABLE on Group.pk == JOIN_TABLE.group_pk join Contact on JOIN_TABLE.contact_pk == Contact.pk where Group.pk == 12
There's a lot going on behind one dot character!
Anyway, to actually fulfill your query, you'd need something like this. I tested this on an actual SQLite CD database, so the table names look strange, but it should still be comprehensible:
select ZGROUP.Z_PK as outer_pk from ZGROUP where "myUID" not in
(select ZCONTACT.ZUID as contact_uid from ZGROUP join Z_1GROUPS on Z_1GROUPS.Z_2GROUPS == ZGROUP.Z_PK join ZCONTACT on Z_1GROUPS.Z_1CONTACTS == ZCONTACT.Z_PK where ZGROUP.Z_PK == outer_pk)
I'm no SQL expert, but my observations are first of all that this query is going to be slow, and second of all that it is kind of a long ways from the NSPredicate that we started with. So it would be only through a great deal of effort that CD could up with an SQL query for what you want to do, and the query that it would come up with would not be much better than a naive implementation in ObjC.
For whatever it's worth, an Apple developer says here that ALL is unsupported in SQLite and the documentation to the contrary is wrong. That documentation is still there in 2013 though, so nobody seems to have done anything about it.
Anyway, what you should actually do is something like this:
NSFetchRequest *fetchRequest = ...
NSArray *result = [moc executeFetchRequest:fetchRequest error:&err];
result = [result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId]];
This will evaluate the predicate in software.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With