NSPredicate for one-to-many relationship - iphone

Assuming a Department entity and an Employee entity with a one-many relationship
Assuming relationship called employees on Department and department on Employee
Assuming Employee has an attribute name
I need to fetch all Departments that don't have an Employee called "Bob"
Based on other answers on SO that I've read, I've tried this:
name = #"Bob";
predicate = [NSPredicate predicateWithFormat:#"ALL employees.name != %#", name];
but it doesn't seem to work. Any ideas?
Thanks.

Do do not need a subquery here:
name = #"Bob";
predicate = [NSPredicate predicateWithFormat:#"NOT ANY employees.name == %#", name];
For some reason, the "ALL" aggregate does not work with to-many relationships, but the "ANY" aggregate works.

Related

NSPredicate to query parent and child

I have a data model setup with three entities, Course, Student, TestScores.
They are linked in too-many relationships like this:
Course <---->> Student <---->> TestScores
So a Course would have several Students, who in turn could have several TestScores (or no test scores)
The Course entity has a Name attribute. TestScores is a simple entity which just contains a testScore int attribute.
I want to be able to get an array of Students who have at least one textScore of 100, ordered by Course name. Is this possible with NSPredicate?
I think you could have your predicate as
ANY testScores.score == 100
Then put it all together in a fetch request:
NSFetchRequest *req = [NSFetchRequest fetchRequestForEntityNamed:#"Student"];
req.predicate = [NSPredicate predicateWithFormat:#"ANY testScores.score == 100"];
req.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"course.name" ascending:YES]];

NSPredicate for searching within relationship entities

I have an entity called Band which has a to-many relationship to a Category entity. The Category entity just contains a categoryName string attribute.
An example record:
Band:
bandName: Kiss
bandCategories: - > BandCategory:categoryName:Glam
- > BandCategory:categoryName:Rock
How would I use NSPredicate to search thru all my Bands for bands which match the Rock category, for example?
According to the NSPredicate Programming Guide you will need to specify the key path to categoryName with the ANY or ALL specifier.
NSString *category = #"Rock";
[NSPredicate predicateWithFormat:#"ANY bandCategories.categoryName == %#", category];

How to set a reference from one object to another?

SOLVED
I misspelled the property department -> Department.
Thanks for the answers and a big SORRY
Hi,
Imagine the obvious model of employees and departments, where each employee must refer to a single department, and each department may have none to many employees.
For Employee I have a pointer to Department, and I set its value to the address of the object of the relevant Department.
For Department I have a NSSet, to which I can add employees.
The problem is that when I create a new employee and a new department, the objects exist in the memory at some place. The reference from Employee to Department, and vice versa, contains these addresses. When I'll fetch the data it will be located at a different location. So it is not clear how the connection between these objects is done.
Here is the code as I understand it should be:
// Fetch Plist object
NSDictionary *plistDictionary = [[NSDictionary alloc] initWithDictionary:plistData];
// Create Department Entity
Department *department = (Department *)[NSEntityDescription
insertNewObjectForEntityForName:#"Department"
inManagedObjectContext:managedObjectContext];
// Set properties
[department setName:[plistDictionary valueForKey:#"department"]];
// Create Employee entity
Employee *employee = (Employee *)[NSEntityDescription
insertNewObjectForEntityForName:#"Employee"
inManagedObjectContext:managedObjectContext];
// Set properties
[employee setFileName:[plistDictionary valueForKey:#"picture"]];
[employee setName:[plistDictionary valueForKey:#"name"]];
// Set relationship
[employee setDepartment:department];
[department addEmployee:employee];
// Save data to core data
[self saveAction];
And here is the Error I get:
2010-04-08 16:06:11.295 Paparazzi2[2656:207] ERROR:saveAction. Unresolved Core Data Save error Error Domain=NSCocoaErrorDomain Code=1570 UserInfo=0x3d1c6f0 "Operation could not be completed. (Cocoa error 1570.)", {
NSLocalizedDescription = "Operation could not be completed. (Cocoa error 1570.)";
NSValidationErrorKey = department;
NSValidationErrorObject = <Employee: 0x3d13a30> (entity: Employee; id: 0x3d16670 <x-coredata:///Employee/t8C950118-C388-4020-8CD9-1F49138A94193> ; data: {
picture = "mike.jpg";
name = "Mike Smith";
department = nil;
});
}
Thanks,
Tzur.
Hey Tzur, one problem with the code you provide is that you're trying to assign the department relationship a value that is not a managed object.
You should first create the Department object if it doesn't already exist, and use that object as the value of the department relationship.
I would replace this line:
[employee setDepartment:[object valueForKey:#"department"]];
With this:
Department *myDepartment = [self fetchDepartmentNamed:[object objectForKey:#"department"]
inManagedObjectContext:[self managedObjectContext]];
if (!myDepartment) {
/* no existing department with that name.. create one */
myDepartment = (Department *)[NSEntityDescription insertNewObjectForEntityForName:#"Department" inManagedObjectContext:[self managedObjectContext]];
[myDepartment setName:[object objectForKey:#"department"]];
}
/* assign the relationship to the department managed object */
[employee setDepartment:myDepartment];
This assumes you have defined a method -(Department *)fetchDepartmentNamed:inManagedObjectContext:] that will build a fetch request and get a Department with the name you provide (also assumes your department names are unique)
Alternatively, you could just add the employee to myDepartment's "employees" relationship, and it would achieve the same result.
Ok, so you have updated your code, and now you have this:
// Set relationship
[employee setDepartment:department];
[department addEmployee:employee];
You should have done, one or the other... not both. Check in the managed object model that you have selected the "Inverse:" of the relationship for both entities. If you set one of the relationships, Core Data will manage the reverse (inverse) of the relationship.
You can't use nil in an NSDictionary. If you haven't synthesized classes to represent your managed objects, you have to use the NSNull class. So, assuming employee is an instance of NSManagedObject and represents your Employee entity:
[employee setValue:[NSNull null] forKey:#"department"];
This is used to sever whatever relationship this instance of Employee had with its previous department. If you do set this to nil, however, the department attribute must be optional or you will have a validation error when you try to save your object graph (context).

Why does the LIKE[c] in my subquery predicate not match this name?

I have two entities: Department and DepartmentInfo. Every Department has one or many DepartmentInfo Objects. Inside DepartmentInfo, there is an departmentName attribute.
I want to fetch all those Department objects which have a specific departmentName. So I make an NSFetchRequest for the Department entity, and use this fetch request:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(departmentName, $s, $s.departmentName LIKE[c] %#).#count > 0", #"Marketing"];
It works, BUT: The LIKE[c] doesn't! I must match against the exact department name. If I do this, I will get no match:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(departmentName, $s, $s.departmentName LIKE[c] %#).#count > 0", #"Mar"];
What could be wrong here?
Since Jason Coco didn't post this as answer, I do it:
Use #"Mar*" and you will match
The use of SUBQUERY here is unnecessary. You can achieve the same results using:
ANY departmentInfo.departmentName LIKE[c] 'Mar*'
Execute that against an array of Department objects and it'll work.

What's better way to build NSPredicate with to-many deep relationships?

I have three entities: EntityA, EntityB and EntityC connected with to-many relationships.
See schema for details:
alt text http://img706.imageshack.us/img706/9974/screenshot20091220at124.png
For getting all instance of EntityA which depend from EntityB.name I use the predicate like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY EntityB.name like 'SomeName'"];
What should be predicate for getting all instance of EntityA which depend from EntityC.name?
I tried query like #"ANY EntityB.entitiesC.name like 'SomeName'" but get exception "multiple to-many keys not allowed here".
Best regards,
Victor
My final solution is to use SUBQUERY.
NSPredicate *p = [NSpredicate predicateWithFormat:#"(name like %#) AND (0 != SUBQUERY(entitiesB, $x, (0 != SUBQUERY($x.entitiesC, $y, $y.name like %#).#count)).#count)", nameA, nameC];
Unfortunately I was unable to expand this query on nsExpression objects.
While I was stopped at the following decision:
First, I get all the EntityC that satisfy the condition EntityC.name equal to 'SomeName'
NSPredicate *p = [NSPredicate predicateWithFormat:#"name like %#", #"SomeName];
...
NSArray *res = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
Then I get an array of EntityB from above query
NSArray *parentBs = [res valueForKeyPath:#"#distinctUnionOfObjects.parent"];
Than get array of EntityB that satisfy the condition EntityB.EntitiesC.name equal to 'SomeName':
NSExpression *leftExpression = [NSExpression expressionForEvaluatedObject];
NSExpression *rightExpression = [NSExpression expressionForConstantValue:parentBs];
NSPredicate *p = [NSComparisonPredicate predicateWithLeftExpression:leftExpression rightExpression: rightExpression modifier:NSDirectPredicateModifier type:NSInPredicateOperatorType options:0];
I repeat the same for EntityA.
The effectiveness of this solution in doubt and I still expect a better solution for this problem.