NSPredicate searching for 1:N - 1:N entities - objective-c

I have an Entity called AcademicYear that has a relationship 1:N with an entity called Subject which has a 1:N relationship with an Entity called SubjectLanguage:
AcademicYear<--->>Subject<--->>SubjectLanguage
I would like to filter the result of a query on AcademicYear in order to be able to gather Subject and SubjectLanguage already filtered by the parameter language.
On runtime i'll use such expression:
academicYear.hasSubjects.hasLanguages.subjectName
and i would like to be sure they are of a specific language filtered by the starting query.
i tried using during the fetch the following predicate (with no result):
NSPredicate *predicateLang = [NSPredicate predicateWithFormat:#"ALL hasSubjects.hasLanguages.language like %#", language];
NEW (Update)
Due to Marcus suggestion i changed the fetch request in this way:
NSError *error;
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString * yearString = [NSString stringWithFormat:#"%d",year];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"belongToAcademicYear.yearId == %# and ANY hasLanguages.language CONTAINS[cd] %#", yearString, language];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Subject"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return [self arrayToMutableArrayConverter:fetchedObjects];
}
then i tested this solution with the following code:
NSArray * currentSubjects = [self.coordinator fetchSubjectThatBelongsToAcademicYear:1];
Subject * currentSubject = currentSubjects[0];
NSUInteger count = currentSubject.hasLanguages.count;
XCTAssertEqual(count, 1, #"riscontrati troppi languages. non è stato fatto il filtro su language");
I found the filter did not work correctly and i found two languages while i expect one. I'm pretty sure the issue is related with the predicate.
Any support is appreciated
kind regards
Nicolò

You should reverse this a little bit. Instead of fetching AcademicYear and then trying to reach over to SubjectLanguage, search for Subject.
Build your NSFetchRequest against Subject and change the predicate to:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"academicYear == %# and ANY languages.language CONTAINS[cd] %#", academicYear, language];
Some comments:
Your relationships should not start with "has". They should just be academicYear and languages. Singular for to-one and plural for to-many.
Your entity SubjectLanguage should not have a property called language. Something that relates to the entity can be less descriptive. name would work well so that it is the "name of the SubjectLanguage".
Update
It worked exactly right. You found the Subject that has the language and the year you are looking for. It returned that Subject and ALL of its relationships. Since there is more than one language associated with the subject it gave you access to all of the languages.
The predicate is correct, your test is flawed. Your test should be insuring that one of the languages is the one you are looking for.

Related

Value for Relation in Core Data using a Predicate

First of all, I have to apologize for that silly question. Unfortunately I couldn't figure out how to implement after reading so many posts. Sorry for that. So, here is my question: I have two entities, regions and counries. Each region belongs to a country and every country has several regions. In my application I chose a country and want to display all its regions.To keep it simple, country and region both have the attribute name and region has a relationship 'country'. Now I have to select all the regions of a country:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *desc = [[model entitiesByName] objectForKey:#"Region"];
[request setEntity:desc];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sd]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"any country LIKE[c] 'aCountry'"];
[request setPredicate:predicate];
That one looks nice, but doesn't work, because country is a relationship and because of this the names of the countries are not stored in the relationship, but in the entity country. I then tried another way:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"any Country.name LIKE[c] 'aCountry'"];
This results in an error:
NSInvalidArgumentException', reason: 'keypath Country.name not found in entity '
Now, what is the best way to get my regions?
If aCountry is an object of the Country entity,
and country is the to-one relationship from Region to Country,
then the following fetch request finds all regions of that country:
Country aCountry = ... ;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Region"];
// ... add sort descriptor (optional)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"country = %#", aCountry];
[request setPredicate:predicate];
But note that if you also have defined an inverse to-many relationship regions from Country to Region then you get all regions of the country just by
NSSet *regionsForCountry = aCountry.regions;
or, if you prefer an array:
NSArray *regionsForCountry = [aCountry.regions allObjects];

A reverse kind of string compare using NSPredicate

I've been searching for this answer all over internet but so far no luck. So I need to consult the smart and nice people here. This is my first time asking a question here, so I hope I am doing this right and not repeating the question.
For all the examples I saw, it's the search string that is a substring of what's stored in the Core Data. On the other hand, I want to achieve the following:
The strings stored in core data are actually sub-strings. I want to do a search by getting all core data rows that have substrings belong to the provided search string.
For ex:
In core data, I have "AB", "BC","ABC","ABCDEF","GH", "ABA"
And in the app I do a search by providing the super-string: "ABCDEF", the result will return "AB","BC","ABC","ABCDEF" but not "GH", "ABA" because these two sub-strings don't belong to the super-string.
How should I setup my predicateWithFormat statement?
This wont' work cuz it's doing the opposite:
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"substring LIKE[c] %#", #"ABCDEF"];
Thanks all!
The reverse of CONTAINS will not work. Also, you will not be able to use LIKE because you would have to take the attribute you are searching and transform it into a wildcard string.
The way to go is to use MATCHES because you can use regular expressions. First, transform your search string into a regex by affixing a * after each letter. Then form the predicate.
This solution has been tested to work with your example.
NSString *string= #"ABCDEF";
NSMutableString *new = [NSMutableString string];
for (int i=0; i<string.length; i++) {
[new appendFormat:#"%c*", [string characterAtIndex:i]];
}
// new is now #"A*B*C*D*E*F*";
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"stringAttribute matches %#", new];
where stringAttribute in the predicate is the name of your NSString attribute of your managed object.
I think this will work:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%# contains self",#"ABCDEF"];
You would use it like this in core data:
-(IBAction)doFetch:(id)sender {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"%# contains desc",#"ABCDEF"];
NSArray *answer = [self.managedObjectContext executeFetchRequest:request error:nil];
NSLog(#"%#",answer);
}
In this example, "desc" is an attribute of the entity "Expense". This correctly retrieves only the rows where "desc" is a substring of "ABCDEF".

Core Data NSPredicate for toMany Relationsihp

I've got a problem in the formulation of my query; this is the scenario:
based on this db schema, I know the naming part sucks a little, but this is
a project which has been already kicked off, so I've to stick with that.
Now, my goal is to select all the characters with a determined pack_id and a certain category_id, i.e. all the characters from pack 1 in category 5, so this's my NSPredicate
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(category_id == %#) AND (charRelationship.pack_id == %#)", [[cat valueForKey:#"category_id"] stringValue], curPack];
[request setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:self.managedObjectContext]];
[request setPredicate:predicate];
NSError *error;
NSArray *result = [self.managedObjectContext executeFetchRequest:request error:&error];
as soon as the compiler tries to execute the fetchRequest, it crashes and goes SIGABRT.
I really hate the fact that xcode is not even giving me a clue about the exception so that I could figure it out myself. So after blindly trying to fix it with no success, I wonder if there's anybody out there who could help me.
I've already red a ton of other threads on SO and elsewhere, but I couldn't find any solution.
thanks a lot
-k-
try using ANY for the relationship:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(category_id == %#) AND (ANY charRelationship.pack_id like %#)", [[cat valueForKey:#"category_id"] stringValue], curPack];
Actually you must not store any id separately for an entity that is associated. E.g.: in your case you could just refer to category from the characters entity like:
[NSPredicate predicateWithFormat:#"catRel = %#", cat];
However, I'm not sure what are you going to do with the snippet. Am I right that you have a Category entity stored in your 'cat' variable, and just going to select it from the database with the predicate? I would say, that a category_id already identifies your category... whatever, try not user "charRelationship.pasck_id == %#" but load the corresponding Character entity and use "charRelationship = %#" where you just put your entity.

Number of elements in NSArray where (....)

I am used to working in C# using the linq extensions (list.select(...).where(...) ext), and I was wondering if there was some way of doing the same sort of thing in Objective-C. This would save me from building a number of rather complicated queries using Core Data, which is great for some things, but perhaps not the best for complex queries (or maybe I'm just uninformed).
Is there some kind of equivalent for linq in Objective-C/Core Data?
EDIT: More specifically, I would like to count the number of elements that fit some criteria. Say my model has a field called date. I am trying to select the distinct dates, and then calculate how many of each date there are. In SQL this would be like a group by, and a COUNT aggregate.
Your question goes from very general ("linq equivalent?") to very specific (computing count by date). I'll just answer your specific question.
Unfortunately, NSArray doesn't have a built-in map or select method, but it does offer NSCountedSet, which will compute what you want:
NSCountedSet *dateSet = [NSCountedSet set];
for (id thing in array) {
[dateSet addObject:[thing date]];
}
for (NSDate *date in dateSet) {
NSLog(#"There are %d instances of date %#", [dateSet countForObject:date], date);
}
Change predicate , and "Date" keys with your props
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"Date"]];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)",sda ];
[fetchRequest setPredicate:predicate];
int c = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
Here was something posted with comes close
filtering NSArray into a new NSArray in objective-c
Anyway AFAIK you don't have some sort of linq in Objective-C but you have. Arrays and Blocks. And Blocks are functions. So you can really filter on anything in there.
Of course, Cora Data has many functions to make complex queries:
In example to get sum of elements, you have two major ways:
first - get your data to NSSet or NSArray and use #sum operator:
//assume that `childs` are NSArray of your child entities and ammount is attribute to sum, and has attributes start (date) and completion (integer)
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)", dzis];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error = nil;
if ([afetchedResultsController performFetch:&error]) {
NSArray *childs = afetchedResultsController.fetchedObjects;
NSNumber *sum=[childs valueForKeyPath:"#sum.ammount"];
}
second is using specific fetch for specific value with added NSExpressionDescription with a sum. This way is harder but better for larger db's
suppose if you have an array of your model objects, you could that with the following statement,
NSArray *distintDatesArray = [array valueForKeyPath:#"#distinctUnionOfObjects.date"];
for ( NSDate *date in distintDatesArray)
{
NSLog (#"Date :%# ,count : %d",date,[[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"date = %#",date]] count]);
}
This will have same effect as the group by query.

How to use NSPredicate to filter based on child elements?

I'm using Core Data; let's say that I have a many-to-one relationship between employees and departments, and employees are stored in an NSSet in each department. I want to find all the departments that only have one employee. How do I do this with Core Data?
I tried the below code and I get an exception saying that MYEmployee doesn't respond to allObjects.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY SELF.employees.allObjects.count == 1"];
singleEmployeeDepartments = [[myModelController allDepartments] filteredArrayUsingPredicate:predicate]];
First of all, you're not using Core Data efficiently here, because you are always fetching all departments from the data store and then filtering the resulting array in memory. You can query the managed object context directly for matching departments, instead of fetching all departments, which can reduce memory consumption and might also be faster if you have a lot of departments.
NSManagedObjectContext *moc = ...;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Department" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"employees.#count == 1"]];
NSArray *singleEmployeeDepartments = [moc executeFetchRequest:request error:NULL];
The other key part is to use the #count operator in the predicate string to query departments that have a specific number of employees.