Set 2 NSPredicates on a NSFetchRequest - ios

I'm using Core Data and have a to-many relationship with the following entities:
Athlete(evals)<-->>Eval(whosEval)
It starts with a table view that lists ALL athletes in the database. Then when you select an Athlete it pulls up their Evals in a table view. The problem is the way I am doing this is through checking their full name. Unfortunately, it is possible for 2 athletes to have the same name. For this reason, I check their parent's name as well, but I think I am doing it incorrectly. Can anyone explain why the following doesn't work and how I should do it correctly? What happens with this code is if 2 Athletes have the same name, they'll share results. Even if their Parent's Name is different.
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSFetchRequest *athleteRequest = [[NSFetchRequest alloc] init];
[athleteRequest setEntity:[NSEntityDescription entityForName:#"Athlete" inManagedObjectContext:_managedObjectContext]];
NSError *athleteError = nil;
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"full == %#", _athletesFullName];
[athleteRequest setPredicate:athletePredicate];
NSArray *results = [_managedObjectContext executeFetchRequest:athleteRequest error:&athleteError];
if([results count] >1){
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"pfull == %#", _athletesParentsFullName];
[athleteRequest setPredicate:athletePredicate];
}
Athlete *athleteSelected;
if([results count] >0){
Athlete *currentAthlete = [results objectAtIndex:0];
athleteSelected = currentAthlete;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"whosEval == %#", athleteSelected];
[request setPredicate:predicate];
NSEntityDescription *eval = [NSEntityDescription entityForName:#"Eval" inManagedObjectContext:_managedObjectContext];
[request setEntity:eval];

Modifying athleteRequest after the request has been executed does not have
any effect on the result. Why not simply
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"full == %# AND (pfull == nil OR pfull == %#)",
_athletesFullName, _athletesParentsFullName];
?
Apart from that, it would probably be better to identify the objects by some
unique identifier (e.g. a unique athlete number) instead of relying on name
and parent's name.

Your scenario is really simple, but for more complex situations, you can use compound predicates.
Read this awesome article from NSHipster for more informations:
http://nshipster.com/nspredicate/

Related

How to add predicate for one to many relation Core Data iOS

I am having 2 table in core data
Product and Option
A product can have multiple options, now I would like to get product having a specific option.
I have no idea how to write predicate where clause for this
Its crashes with error ( *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
) when I try below code
-(Product*)getProdcutFromDB{
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *orderEntity = [NSEntityDescription
entityForName:#"Product" inManagedObjectContext:context];
[fetchRequest setEntity:orderEntity];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#" options.optionID == '14'"];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
Product* orderEntityForProdcut=nil;
if (fetchedObjects.count>0) {
orderEntityForProdcut=(Product*)[fetchedObjects objectAtIndex:0];
NSLog(#"%#",orderEntityForProdcut.productName);
NSLog(#"%#",orderEntityForProdcut.options);
return orderEntityForProdcut;
}
return nil;
}
For this, you have to use SUBQUERY().
(The separate string is to make it easy to read on SO.)
NSString *predicateFormat = #"SUBQUERY(options, $o, $o.optionID == %#).#count > 0"; // The "> 0" condition could also be "== 1", depending on your need.
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat, optID];
Aside: It's best not to hard-code property names in strings. It's better to write the above as:
// These would be defined somewhere that could be imported as needed.
NSString *const kOptionsKey = #"options";
NSString *const kOptionIDKey = #"optionID";
NSString *predicateFormat = #"SUBQUERY(%K, $o, $o.%K == %#).#count > 0";
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat,
kOptionsKey, kOptionsIDKey, optID];
The idea is that if the property name is changed, a single constant is all that needs updating. This also prevents the more common occurrence of a typo in the property name within a string.
Thanks #Avi for you answer, My problem has been resolved
Now update code is here using Multiple Predicate with AND condition
After this code I am able to get list of Product having optionId="" and optionId="" and so on.....
-(Product*)getProdcutFromDBNew{
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *orderEntity = [NSEntityDescription
entityForName:#"Product" inManagedObjectContext:context];
[fetchRequest setEntity:orderEntity];
// These would be defined somewhere that could be imported as needed.
NSString *const kOptionsKey = #"options";
NSString *const kOptionIDKey = #"optionID";
NSString *predicateFormat = #"SUBQUERY(%K, $o, $o.%K == %#).#count > 0";
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:predicateFormat,
kOptionsKey, kOptionIDKey, #"12"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:predicateFormat,
kOptionsKey, kOptionIDKey, #"14"];
// Add the predicates to the NSArray
NSArray *subPredicates = [[NSArray alloc] initWithObjects:predicate1, predicate2, nil];
NSCompoundPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
// NSPredicate* predicate = [NSPredicate predicateWithFormat:#" ANY options.name == 'Size'"];
[fetchRequest setPredicate:compoundPredicate];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
Product* orderEntityForProdcut=nil;
if (fetchedObjects.count>0) {
orderEntityForProdcut=(Product*)[fetchedObjects objectAtIndex:0];
NSLog(#"%#",orderEntityForProdcut.productName);
NSLog(#"%#",orderEntityForProdcut.options);
return orderEntityForProdcut;
}
return nil;
}

Using NSPredicate and Joins

I have 2 tables: Users and Comments like so:
Users
user_id (PK)
name
Comments
comment_id (PK)
user_id
comment
I want to get a list of all users and their comments.
How is this done using NSPredicate?
Thanks.
As far as I understood your question, you are using core-data.
First of all, you have to make a small change in your managed object model.
Entity
Users
Attributes
user_id
name
Entity
Comments
Attributes
comment_id
comment
Relationships
Relationship - - - Desination - - - Inverse
user - - - Users - - - NO Inverse
Now , whenever you enter the data for Comments object, associate a user relationship with it as
comment.user=theUser
where theUser is a 'Users' object which you can get from 'Users' table by comparing user_id, for that you can use a simple method as by calling the following method for the userID that you got from comment data
theUser= [self isUserAlreadyExistsWithUserID:userID];
where the method definition could be like in Users NSManagedObject subclass
+(NSManagedObject*)isUserAlreadyExistsWithUserID uniqueValue: (id)uniqueValue
{
NSManagedObjectContext* context=[self managedObjectContext];
NSError*error=nil;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Users" inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"user_id = %#",uniqueValue];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = entity;
[fetchRequest setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
if(objects && objects.count>0)
{
return [objects objectAtIndex:0];
}
else
{
return nil;
}
}
Now, to fetch comments for particular user in comments NSManagedObject subClass:
+(NSFetchedResultsController*)getCommentsForUser:(Users*)user
{
NSString* cacheName=#"Root";
[NSFetchedResultsController deleteCacheWithName:cacheName];
NSManagedObjectContext* context=[self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([self class]) inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
NSPredicate* predicate=[NSPredicate predicateWithFormat:#"user=%#",user];
fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context sectionNameKeyPath:nil
cacheName:cacheName];
return theFetchedResultsController;
}
Hope this helps.
I think, you need this query:
SELECT Users.*,Comments.comment_id,Comments.comment FROM Users LEFT JOIN Comments ON Users.user_id = Comments.user_id;
This will return list of all users along with their comments. You can modify it, as per your specific requirements.
Are you using CoreData? if yes then use try the following code:
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Users" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
[request setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObjects:#"Comments",nil]];
[request setIncludesSubentities:YES];
NSArray* returnArray = [self.managedObjectContext executeFetchRequest:request error:&error];
if([returnArray count] > 0)
{
Users* user = [returnArray objectAtIndex:0];
NSLog(#"%# %# %#", user.user_id, user.name, user.userComments.comment);
}
Also, CoreData uses relationships instead of foreignKeys. Therefore, to get this working you need to setup an inverse relationship userComments (many to one) from Comments to Users.
It is better not to use NSPredicate when making a query. You can simply write a JOIN query. As Apple's documentation also states that the NSPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering. Also it will be easier to use Key-Value Coding. Here is a good explaination to how it can be used. Lets assume you have NSSet of Comments. You can do something like :
[Comments valueForKeyPath:#"users.user_id"];
But, if you really want to use NSPredicate following is the possible way:
NSFetchRequest *fetchReq = [[NSFetchRequest alloc] initWithEntityName:#"Users"];
NSError *error;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(user_id == %#)",Comments.user_id];
[fetchReq setPredicate:predicate];
NSArray *result = [self.managedObjectContext executeFetchRequest:fetchReq error:&error];

How to fetch specific records in Core data

I am a new bid in iOS development. I am using NSManagedObject of Core Data to perform Insert and Fetch operations. It works perfectly fine. But the problem is, I want to fetch only some selected records (where condition in MySQL) from the table.
For e.g. "select name from users where city='Pune'";
I found NSPredicate to fetch filtered data. But it gives all the data in array and not just the selected one. For e.g. if result for above query is:
Anu
Then the NSPredicate result will give:
fname = Anu
lname = Padhye
city = Pune
id = 3
Is there a way to only fetch selected record/s in iOS Objective-c? Following is the code snippet I am using for NSManagedObject:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"User"];
valueData = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSEntityDescription *productEntity=[NSEntityDescription entityForName:#"User" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
[fetch setEntity:productEntity];
NSPredicate *p=[NSPredicate predicateWithFormat:#"id == %d", 3];
[fetch setPredicate:p];
//... add sorts if you want them
NSError *fetchError;
NSArray *fetchedProducts=[valueData filteredArrayUsingPredicate:p];
Try this:
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"User"];
request.predicate = [NSPredicate predicateWithFormat:#"city == %# && id == %d", #"Pune", 3];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"id" ascending:YES]];
NSArray *results = [managedObjectContext executeFetchRequest:request error:nil];
Results array should now contain all records who have Pune as their city with the id of 3.

Fetch objects from an entity of one to many relationship core data

I was scratching my head all the way through to get this problem solved my self but unfortunately i couldn't.
So my problem is i have multiple values corresponding to one lectures so i made lectureTpPbl,lectureToProfessor,lecturetoStudyProgram relation of type one to many relationship as shown in image.
i saved into the DB like below:
-(void)saveToDatabase:(NSMutableDictionary *)inData{
LectureDetails *lectureDetail = (LectureDetails *)[NSEntityDescription insertNewObjectForEntityForName:#"LectureDetails" inManagedObjectContext:context];
[lectureDetail setAverageRating:[inData valueForKey:kLectureAverageRating]];
[lectureDetail setCategoryID:[inData valueForKey:kLectureCategoryID]];
[lectureDetail setCategoryName:[inData valueForKey:kLectureCategoryName]];
[lectureDetail setLectureID:[inData valueForKey:kLectureID]];
[lectureDetail setSemesterID:[inData valueForKey:#"SemesterID"]];
[lectureDetail setStartTime:[inData valueForKey:kLectureStartTime]];
[lectureDetail setRatingType:[inData valueForKey:kRatingType]];
[lectureDetail setMonth:[NSNumber numberWithInt:[[[[inData valueForKey:kLectureDate] componentsSeparatedByString:#"-"] objectAtIndex:1] integerValue]]];
[lectureDetail setDateOnly:[NSNumber numberWithInt:[[[[inData valueForKey:kLectureDate] componentsSeparatedByString:#"-"] objectAtIndex:2] integerValue]]];
for (id prof in [inData valueForKey:#"Professors"]) {
ProfessorsInLecture *profData = (ProfessorsInLecture *)[NSEntityDescription insertNewObjectForEntityForName:#"ProfessorsInLecture" inManagedObjectContext:context];
profData.professorID=[NSNumber numberWithInt:[[prof valueForKey:#"ProfessorID"]integerValue]] ;
profData.professorFirstName=[prof valueForKey:#"ProfessorFirstName"];
profData.professorLastName=[prof valueForKey:#"ProfessorLastName"];
profData.ProfessorUserName=[prof valueForKey:#"ProfessorUserName"];
[lectureDetail addLectureToProfessorObject:profData];
}
for (id prog in [inData valueForKey:#"StudyPrograms"]) {
StudyProgramInLecture *stdyProgramData = (StudyProgramInLecture *)[NSEntityDescription insertNewObjectForEntityForName:#"StudyProgramInLecture" inManagedObjectContext:context];
stdyProgramData.sMID=[prog valueForKey:#"SMID"];
[lectureDetail addLectureToStudyProgramObject:stdyProgramData];
}
for (id pblData in [inData valueForKey:#"PBLs"]) {
PBLinLectures *pbl = (PBLinLectures *)[NSEntityDescription insertNewObjectForEntityForName:#"PBLinLectures" inManagedObjectContext:context];
[pbl setLecturePBLID:[pblData valueForKey:#"LecturePBLID"]];
[pbl setPBLID:[pblData valueForKey:#"PBLID"]];
[lectureDetail addLectureToPBLObject:pbl];
}
NSError *error1;
if (![context save:&error1])
{
//NSLog(#"ERROR--%#",error);
abort();
}
}
all the data is getting save correctly but i am getting problem in fetching the data.
Now suppose i have to fetch a lecture whose semesterID=7 and month=12 and day=20 and (in relation table)sMID=2 and (in relation table)pBLID=4.
what i have done so far is to fetch is
-(NSMutableArray *)fetchDataforMonth:(NSNumber *)month andDate:(NSNumber *)dateOnly
{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"LectureDetails" inManagedObjectContext:context];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY lectureToPBL.pBLID ==%#", [NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"pBLID"]integerValue]]];
NSPredicate *pred1 =
[NSPredicate predicateWithFormat:#"(dateOnly == %#)",dateOnly];
NSPredicate *pred2 =
[NSPredicate predicateWithFormat:#"(month == %#)",month];
NSPredicate *pred3 =
[NSPredicate predicateWithFormat:#"(semesterID == %#)",[NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"semesterID"]integerValue]]];
NSPredicate *pred4 = [NSPredicate predicateWithFormat:#"ANY lectureToStudyProgram.sMID ==%#", [NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"programID"]integerValue]]];
NSArray *compPredicatesList = [NSArray arrayWithObjects:pred1,pred2,pred3,predicate,pred4, nil];
NSPredicate *CompPrediWithAnd = [NSCompoundPredicate andPredicateWithSubpredicates:compPredicatesList];
[request setPredicate: CompPrediWithAnd];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
return mutableFetchResults;
}
If there is one sMID and one pBLID then fetching is working fine otherwise i am not getting an object in fetch result.
Suppose i have a lecture whose semesterID is 7 and month=12 and day=20 and have multiples sMID such as 1,2,3 and multiples pBLID such as =4,5,6
and i want to fetch a lecture whose month=12 and day=20 and sMID=2 and pBLID=4
Can anybody tell me how to do that?
Any Help will be appreciated.
Thanks
First filter the Lecturers whose month=12 and day=20 from LectureDetails entity:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(month == %#) AND (dateOnly == %#) ",month, dateOnly];
[request setPredicate: pred];
NSError *error;
NSArray *lectureList = [context executeFetchRequest:request error:&error];
then filter this array with pBLID and sMID.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(ANY lectureToStudyProgram.sMID ==%#) AND (ANY lectureToStudyPBL.pBLID ==%#)", sMID, pBLID];
lectureList = [lectureList filteredArrayUsingPredicate:predicate];
Now lectureList will give you the result.

Core Data NSFetchrequest integer

I have the following scenario. I have an app that handles data by using Core Data. I have an entity called "Brothers" which has 3 attributes: Name, Status, Age.
lets say that I have 1 record on my database that has:
-Name ==> Joe (String)
-Status ==> Married (String)
-Age ==> 28 (Integer)
I have an UIlabel a UItextfield and a Button. I want to be able to type the name (in this case Joe) on the UItextfield press the button and display the age (in this case 28) in the UILabel. I would also like to store the age value into a variable type integer so I can make some calculations with it later on.
below the code that I have inside the button.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age %d", [self.age.text integerValue]];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
self.displayLabel.text = integer;
Update #1
I updated the code that i have inside the button and now i am able to search by the name and display the age. I'm still looking into storing the age as an integer into a variable so i can use it later on.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname like %#", self.firstnameTextField.text];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
if(integer.count <= 0){
self.displayLabel.text = #"No records found";
}
else {
NSString *age;
for (NSManagedObject *object in integer) {
age = [object valueForKey:#"age"];
}
self.displayLabel.text = [NSString stringWithFormat:#"age: %#",age];
}
}
A predicate is an expression. If the expression evaluates to true then the predicate is satisfied. So if you were searching by age you'd use e.g.
[NSPredicate predicateWithFormat:#"age = %d", [self.age.text integerValue]]
Or by name:
[NSPredicate predicateWithFormat:#"name = %#", someNameOrOther]
Or by both:
[NSPredicate predicateWithFormat:#"(name = %#) and (age = %d)", someNameOrOther, [self.age.text integerValue]]
A fetch request gets the actual NSManagedObjects. So you'd get back an array of Brothers. Therefore you probably want something more like this to output a name:
if([array count])
self.displayLabel.text = [array[0] name];
Or for an age:
...
self.displayLabel.text = [[array[0] age] stringValue];
Or whatever other property you're outputting.
I think your predicate is wrong, the proper format would be this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age.integerValue == %d", [self.age.text integerValue]];

Resources