Many To Many NSPredicate With NSFetchedResultsController - core-data

I am having trouble creating an NSPredicate object based on my data model configuration.
I have 2 entity's one called Bob and one called Fred. Bob has a many to many relationship called 'hasFreds' (the reverse is hasBobs) with Fred and Fred has a property hasEaten of type BOOL.
I want to fetch a list of Bob's where every Bob has at least one Fred with hasEaten=YES. If there are no Freds with hasEaten=YES, I want to return all Bobs with no Freds.
This is what I have so far and it doesn't quite work (it doesn't meet the "If there are no Freds with hasEaten=YES, I want to return all Bobs with no Fred" condition):
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)"
,#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fr managedObjectContext:[[self appDelegate] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController.delegate = self;

I think that from a concept point of view your conditions are not really one query. You want to make two queries, one based on the outcome of the other one. This is not really what SQL queries, let alone object graph predicates, were conceived for.
Thus, I believe the simplest and most intuitive solution should be to have two versions of your fetched results controller.
You create the other version by changing the predicate dynamically. (I am using this pattern a lot e.g. for searching.) Because a subquery anyway results in more than one trip to the persistent store, you could just make one efficient fetch to evaluate the condition before setting the predicate for your fetched results controller.
After setting your predicate, remove it if necessary:
if ([context countForFetchRequest:request] == 0) {
request.predicate = nil;
}
// continue creating your FRC with the request
Note that countForFetchRequest is very efficient and takes up no memory.
To update,
self.fetchedResultsController = nil;
[self.tableView reloadData]; // or collection view
If you need to take advantage of the FRC's cache feature, a solution with two separate FRCs following the above pattern would also be feasible.

Thanks to the user Mundi, I was able to come up with a solution. First was to implement a method called getCorrectPredicate.
-(NSPredicate *)getCorrectPredicate{
NSPredicate *predicate1;
NSFetchRequest *fr;
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)",#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
if([[[self appDelegate] managedObjectContext] countForFetchRequest:fr error:nil] == 0){
// we have a none situation
self.predicateState = [NSPredicate predicateWithFormat:
#"hasFreds.#count=0"];
}else{
self.predicateState = predicate1;
}
return self.predicateState;
}
Second was to check in the NSFetchedResultsController delegate method controllerDidChangeContent whether or not the predicate changes.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[_expensesTable endUpdates];
[[[self frc] fetchRequest] setPredicate:[self getCorrectPredicate]];
[self performFetch];
}

Related

how to create compoundpredicate within nsFetchRequest that filters by two parameters of different entity

Some background info on my datamodel:
manufacturer <-->> item <<-->> tag
I currently generate a list of items by a fetchrequest:
- (NSFetchRequest*) rankingRequestForItem:(Item*)item {
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF != %#",item.objectID];
r.resultType = NSDictionaryResultType;
r.resultType = NSDictionaryResultType;
r.propertiesToFetch = #[[self objectIDExpressionDescription],#"itemName",
[self rankingExpressionDescriptionForTags:[item mutableSetValueForKey:#"itemToTag"]]];
r.predicate = p;
r.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"itemName" ascending:YES]];
return r;
}
This generates a list of all items. I want to filter it for items that have a relationship to a specific manufacturer. So I'm adding a predicate after the listing of all items and it sorts by selectedManufacturer.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"itemToMa = %#", selectedManufacturer];
This works, but is grabbing a lot of items that will be filtered out. With large data sets I'm assuming will become slower and slower as it searches all items rather than just the ones associated with one manufacturer. I want to filter for items within the initial 'rankingRequestForItem' method.
Is it possible to move the above predicate with the top predicate and create a compoundpredicate?
I would not worry about performance. Core Data manages that pretty well under the hood. Sometimes the order of the predicates matters, so maybe put the manufacturer filter first.
You can combine the predicates in one as suggested in the comment to your question, or use compound predicates -- the result is pretty much the same.

predicate subquery to return items by matching tags

I have a many-to-many relationship between two entities; Item and Tag. I'm trying to create a predicate to take the selectedItem and return a ranking of items based on how many similar tags they have. So far I've tried:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(itemToTag, $item, $item in %#).#count > 0", selectedItem.itemToTag];
Any other iterations that have failed. It currently only returns the selectedItem in the list. I've found little on Subquery. Is there a guru out there that can help me refine this?
Thanks in advance for the help!
Edited 9June
The good news is with Dan's code I'm able to populate the tableview with items! Unfortunately ranking numbers are 0.
Solution
I originally tried searching for tags by ID rather than name. Note the two predicate options in 'rankingExpressionDescriptionForTags:' I do not have unique identifier to my tags and use the second of the two options. Thanks Dan!
A predicate is only the beginning.
First take a look at THIS VERY similar question.
Assuming your model has an Item and Tag entities, related in a many-to-many relationship:
Item.tags <<-->> Tag.items
The answer:
- (NSExpressionDescription*) rankingExpressionDescriptionForTags:(NSSet*)tags
{
NSPredicate* p = [NSPredicate predicateWithFormat:#"SUBQUERY(tags,$t,$t IN %#).#count > 0",tags];
//if your tags are not unique (meaning you only like to match the names of tags)
//change the predicate to:
//p = [NSPredicate predicateWithFormat:#"SUBQUERY(tags,$t,$t.tagName IN %#).#count > 0",[tags valueForKey:#"tagName"]];
NSExpression* rankExpresion = [(NSComparisonPredicate*)p2 leftExpression];
NSExpressionDescription* rankExpDesc = [[NSExpressionDescription alloc] init];
rankExpDesc.name = #"ranking";
rankExpDesc.expression = rankExpresion;
rankExpDesc.expressionResultType = NSInteger64AttributeType;
return rankExpDesc;
}
- (NSExpressionDescription*) objectIDExpressionDescription
{
NSExpressionDescription* expDesc = [[NSExpressionDescription alloc] init];
expDesc.name = #"objectID";
expDesc.expressionResultType = NSObjectIDAttributeType;
expDesc.expression = [NSExpression expressionForEvaluatedObject];
return expDesc;
}
- (NSFetchRequest*) rankingRequestForItem:(NSManagedObject*)item
{
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF != %#",item.objectID];
r.resultType = NSDictionaryResultType;
r.propertiesToFetch = #[[self objectIDExpressionDescription],
[self rankingExpressionDescriptionForTags:[item mutableSetValueForKey:#"tags"]]];
r.predicate = p;
return r;
}
Note:
The resulting array contains dictionaries
(AFAIK) You will have to sort the resulting array in-memory after the fetch if you like to sort by ranking
You cannot use a fetched results controller to track changes in these objects
You can use a FRC to display these items

NSPredicate on nested object / NSSet to filter the result during NSFetchRequest

I want a simple predicate that returns me all the groups which have mode = 0 and the mode of the enrollments in the group = 0
To be precise i need a predicate to access the nested object properties.
Somehow a predicate like:
[NSPredicate predicateWithFormat:#"mode = 0 AND enrollments.Enrollment.mode = 0"]
the above predicate is wrong and obviously doesn't work.
EDITED:
I have given a go to the following predicate too but been unsuccessful.
[NSPredicate predicateWithFormat:#"mode = 0 AND ALL ( SUBQUERY(enrollments,$varEnrollment,$varEnrollment.mode = 0))"]
I need result which contains all groups that are active (group.mode = 0) AND with all enrollee's that are active (enrolles.mode = 0)
but for me this predicate doesn't work.
From your question and the comments I guess that you want
[NSPredicate predicateWithFormat:#"mode = 0 AND (ALL enrollments.mode = 0)"]
UPDATE
It seems that the ALL aggregate does not work. It throws
'NSInvalidArgumentException', reason: 'Unsupported predicate (null)'
as #yunas noticed. This has also been noticed previously, e.g.
Core Data, NSPredicate and to-many key
Crash using Aggregate Operation: "ALL" in a Core Data iOS Application
On the other hand, the ANY aggregate works just fine. So as a WORKAROUND, one can replace ALL with an equivalent ANY expression:
[NSPredicate predicateWithFormat:#"mode = 0 AND NOT(ANY enrollments.mode != 0)"];
And this actually works (I have tested it)!
UPDATE 2
During the discussion it has become clear that yunas wants to display a table with one row for each group with mode=0, and within the table row display all enrollments of that group with mode=0.
First solution (probably the better one): Find all admissible groups first with the method given above. For each row (in cellForRowAtIndexPath:) filter the enrollments for that group and draw the cell.
Second solution: Fetch all enrollments and sort them by group. This requires only one fetch request, but the result is not so suitable as table view data source. The fetch request would look like this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Enrollment"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"mode = 0 AND group.mode = 0"];
request.predicate = predicate;
NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:#"group.name" ascending:YES];
NSSortDescriptor *sort2 = [NSSortDescriptor sortDescriptorWithKey:#"share" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObjects:sort1, sort2, nil];

CoreData - NSPredicate results if relationship has data

I have a many-to-many relationship between tables, and I populate a tableView with Activities.
For that i user a simple NSPredicate like this:
request.predicate = [NSPredicate predicateWithFormat:#"deleted == %#", [NSNumber numberWithBool:NO]];
How can I do to show only the Activities that has Members attached to it?
I think that in the NSPredicate I have to do some count so that only the Activities with count > 0 are returned. Is that so?
How?
(i'm newbie in coredata...)
Thanks,
RL
You need to add a subquery to your predicate acting on the CompanyActivity entity as follows:
[[NSPredicate predicateWithFormat:#"deleted == %#" && (0 >= SUBQUERY(Members, $sub, $sub.deleted == %#).#count)", [NSNumber numberWithBool:NO] [NSNumber numberWithBool:NO]];
The first part of the predicate returns objects which have not been deleted, the second one related to the subquery will take care of retrieving all those CompanyActivity objects whose Members have not been deleted.

Core Data fetches based on properties of 'ordered' relationships

My app has smart folder like functionality: a predicate is setup with a NSPredicateEditor and used to fill the folder with a fetch request.
The entity used in the search has a to-many relationship. The relationship is ordered, in the sense that an index is stored in the destination entity for sorting purposes.
My problem is that I would like to build in a rule based on the last values in the ordered relationship, but I can't figure out how to build a predicate to do this, because the relationship is not an array. Core data doesn't actually know about the order.
I have a readonly property on the class that returns the ordered items, but this doesn't seem to help with the fetch request because the property is not available in the core data store.
The only option I can think of is to de-normalize and store the last items in the relationship ordered in a separate property. Is that the only solution?
Well, assuming I have understood the problem correctly, I'd do it like this. Lets say you've got two entities, TopEntity has a (NSString *)name property and a to-many relationship to MyEntity which has a (NSString *)data property and (NSInteger)order property.
Lets say you want the TopEntity objects which match a given string, and whose MyEntity orders are satisfy a certain condition, then you can do it with two predicates and an NSFetchRequest like so....
NSManagedObjectContext *context = [self managedObjectContext];
// Create some top level entities
TopEntity *aTop = [TopEntity insertInManagedObjectContext:context];
aTop.name = #"This is Your Name";
TopEntity *bTop = [TopEntity insertInManagedObjectContext:context];
bTop.name = #"This aint a Name";
TopEntity *cTop = [TopEntity insertInManagedObjectContext:context];
cTop.name = #"This is My Name";
// Add some data
NSInteger i, len = 30;
for(i=0; i<len; i++) {
// Create a new object
MyEntity *entity = [MyEntity insertInManagedObjectContext:context];
entity.orderValue = i;
entity.data = [NSString stringWithFormat:#"This is some data: %d", i];
if(i < 10) {
[aTop addObjectsObject:entity];
[entity addTopObject:aTop];
} else if (i < 20) {
[bTop addObjectsObject:entity];
[entity addTopObject:bTop];
} else {
[cTop addObjectsObject:entity];
[entity addTopObject:cTop];
}
}
// Save the context
NSError *error = nil;
[context save:&error];
// A predicate to match against the top objects
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name BEGINSWITH %#", #"This is"];
// A predicate to match against the to-many objects
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"ANY objects.order < %d", 5];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"TopEntity" inManagedObjectContext:context]];
[fetch setPredicate:predicate];
NSArray *result = [[context executeFetchRequest:fetch error:&error] filteredArrayUsingPredicate:secondPredicate];
for(TopEntity *entity in result) {
NSLog(#"entity name: %#", entity.name);
}
So, essentially you can just wrap the results of your fetch request with another predicate and use the ANY keyword.
I've got no idea how efficient that is, but it works for this case. Running the above will output "This is Your Name" i.e. it matches the first TopEntity.
I don't think there's a way to limit to n results in a predicate, only at the fetch request level.
Aside from referencing the last n items in a relationship as you mentioned, you might try a boolean attribute "lastN" and flip them on/off when you curate the order of the list (say, during user-initiated sort or drag-and-drop reordering).
Alternatively, you could create a separate fetch request for each searched thing that sorts by your sort key, ordered descending, and is limited (via -setFetchLimit: ) to n results.
Tracking this as a relationship or an attribute is somewhat "messy" whereas the fetch limit is more expensive (because of multiple round trips). If your reordering is done by one-off user actions, it might be better performance-wise to use the relationship or attribute approach since the work is amortized rather than done all at once in a series of fetches. I haven't found a better way myself and will follow this one closely. :-)

Resources