NSFetchRequest GroupBy an attribute of to-many relationship - ios

I have been persisting some records in core data.
Records have to-many relationship.
searching the persist data by to-many relationship seems to be simple and I used "SUBQUERY" to achieve it. I am facing problem with grouping records.
I need to group "ZCMORecord" by stringValue of "ZCMORecordValue"
Since "ZCMORecordValue" is a to-many relationship.
I even need to group ZCMORecords with one or more "ZCMORecordValue".
NSManagedObjectContext *context= [[[UIApplication sharedApplication]delegate ]managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *valueDesc = [NSEntityDescription entityForName:#"ZCMORecord" inManagedObjectContext:context];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setFetchLimit:PAGE_SIZE];
NSPropertyDescription *recordRelationShip = [fetchRequest.entity.relationshipsByName objectForKey:#"recordValueSet.stringValue"];
[fetchRequest setPropertiesToGroupBy:[NSArray arrayWithObjects:recordRelationShip,nil]];
NSError *error;
id fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
Getting errors when I try this.
SELECT clauses in queries with GROUP BY components can only contain
properties named in the GROUP BY or aggregate functions
I need to fetch records in group.
I could't figure out what to set in "setPropertiesToGroupBy:" and "setHavingPredicate:" to achieve proper results.
OKAY!! Example of how I Store my data
ZCMORecordValues
id stringValue<NSString> dateValue<NSDate> record
name Alex - record1<ZCMORecord>
DOB - 10/10/1990 record1<ZCMORecord>
name Anto - record2<ZCMORecord>
DOB - 05/05/1990 record2<ZCMORecord>
name Max - record3<ZCMORecord>
DOB - 10/10/1990 record3<ZCMORecord>
name Mary - record4<ZCMORecord>
DOB - 01/01/1990 record4<ZCMORecord>
Now I want to group my "ZCMORecord" with with respect to DOB.
Any suggestion on changing the model to facilitate grouping is also welcome

The easiest way to do this is to use a NSFetchedResultsController, fetch the record values and pass the stringValue as the sectionNameKeyPath.

Related

Core Data, how can i find and delete managed objects effectively

My app sends a get request to a server with a date (date of last update) to update its content (2 entities in core data, no relationships, all attributes are strings)... most of the time it only receives new content but sometimes it also receives the old content that needs to be updated instead of just inserted. 1 table is pretty straight forward I just get the ids (id from server not form core data) of the items that are going to be updated in an array and I make a fetch of those items and then delete them. After that I insert the updated items as they were new. This is how I delete them:
-(void)deleteOfEntity:(NSString*)entityName theItemsWithIds:(NSArray*)ids{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext: [self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"(id IN %#)", ids]];
NSError *error;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if(fetchedObjects && [fetchedObjects count]>0){
for (NSManagedObject* toDelete in fetchedObjects) {
[[self managedObjectContext] deleteObject:toDelete];
}
}
}
because the attribute name which identifies each item is the ID as usually. But the other table needs 2 attributes to identify items, like a composite key. How do I build up the array of "ids"? an array with arrays of 2 values indicating the composite key? and the predicate? I just want to know if it is possible to do this efficiently, if not I can always fetch all the items and check 1 by 1 but for that I need a for inside another for and that is to ugly. Any help is appreciated.
When you designed the database you should have created a unique key field, even if it is just a composite of the two values. That would have made this question go away.
However, to solve the problem now you need to do a fetch on one key similar to what you have above and then loop over the second key. However, you do not need to do a loop within a loop. You can use a second NSPredicate against that returned array to get the objects to modify.
Less ugly and quite efficient since you are only going to disk once and the second filter is happening in memory.
Update
#DuncanGroenwald is correct that you still must loop through every object, but there is looping and there is looping.
A developer writing a for loop and then doing a string compare inside of that for loop is significantly less efficient then letting the frameworks perform the same option. How? With a NSPredicate against the array:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"someValue IN %#", idArray];
NSArray *results = [origArray filteredArrayWithPredicate:predicate];
If you test both options, the predicate will run significantly faster.
Well what I did is to create another attribute named "identifier" which is a stringWithFormat:#"%#%#",key1,key2, it doesn't matter the extra string in coredata because it suppose to have just a few managed objects in that entity

NSFetchedResultsController with data from other model

I'm trying to build app with UITableView with data from NSFetchedResultsController. The problem is - I need to show some additional data from other models in table view.
Assume I have 2 models: Students and Groups (I think the relation is obvious: group consists of many students and student can be only in one group). I'm trying to build UITableView with list of groups. I would also like number of students in each group.
(NSFetchedResultsController *) fetchController {
if (!_fetchController) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"GroupModel" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
[fetchRequest setEntity:entity];
NSPredicate *messageInChatPredicate = [GroupModel getMyChatsPredicate];
[fetchRequest setPredicate:messageInChatPredicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
_fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:#"main"];
_fetchController.delegate = self;
}
return _fetchController;
}
I wonder how I can I add additional information (such as number of students in each group)?
Why not just use group.students.count to get the value from the group entity? You only need one fetchedResultsController and I think you will find Core Data performance is fine.
Your terminology is a bit confusing, usually a model is a core data model created in Xcode and has a number of entities e.g. Group, Student. Sometimes you can use two different models and/or different versions of the models.
Your objects are usually know as Entities and both would belong to the same Core Data model.
EDIT:
To get the count of subgroups, such as girls or boys you can do the following
NSMutableArray *boys = [[NSMutableArray alloc] init];
[NSPredicate predicateWithFormat:#"gender = %#",#"MALE"];
[boys addObjectsFromArray:[group.students filteredArrayUsingPredicate:predicate]];
NSLog(#" boys count is %d", boys.count);
Assuming of course that gender is an attribute of Student.
Create another NSFetchedResultsController that is responsible for the other model (i.e student).
You have basically two options to get additional information such as the number of students in each group.
The first option is to count the students in the Group-Student relation. To serve this request the NSManagedObjectContext will load all students in memory to get a suitable result. Personally, I don't really like this option.
The second option is to store the number of students who belong to a group in a property of the group so that it can be directly add. Yes, you have to maintain the correct number manually. Depending on the amount of data that has to be loaded, this approach is more preferable. Crucially since it's way fas

CoreData: fetchRequest on a subTable

How can I add a Subcategorie to a Categorie?
And how can I get the specific Subcategorie's of a Categorie?
I need something like
get Subcategorie.denumire where Categorie.denumire == "somename"
and
add mySubcategorie to Categorie.denumire where Categorie.denumire == "somename"
How can I do this? How can I get the name of a subtable's parent table and the names of the subtables of a table?
When you generate NSManagedObject Entities, Goal class will have an NSSet called toMinorGoal (assuming, your toMinorGoal is unordered relationship). Also, XCode will generate 4 accessory methods to add/remove MinorGoal objects to/from relationship.
If you need to fetch MinorGoals object, you would just need to get Goal object and then access its toMinorGoals NSSet that will contain all of its MinorGoal objects. Alternatively, you can just fetch MinorGoal objects, but these will return every single one of them (if you don't specify how many you want).
This is an approximate example of generated accessors XCode will provide you with:
- (void)addtoMinorGoaObject:(MinorGoal *)value;
- (void)removetoMinorGoalObject:(MinorGoal *)value;
- (void)addtoMinorGoal:(NSSet *)value;
- (void)removetoMinorGoal:(NSSet *)value;
Hope it helped you.
After a few day's of trying different solutions I finally figured this out thanks to this tutorial on CoreData :
http://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started
I fetched all the subtables Subcategorie of the table Categorie like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Subcategorie"
inManagedObjectContext:self.managedObjectContext];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"categorii.denumire == %#",self.title];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
NSError *error;
self.listaElementeBD = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
self.title is the denumire of the Categorie, hope this helps.

Value of sectionNameKeyPath for many-to-many relationship in Core Data

I have a Core Data model with three entities: Notification, Group and Customer. These are the relationships between them:
A customer belongs to many groups and a group can have many customers.
A notification is sent (belongs) to a group and a group can receive (have) many notifications.
I would like to show all notifications in a UITableView grouped by customer. I have created a NSFetchedResultsController like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.fetchBatchSize = 10;
fetchRequest.predicate = nil;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Notification"
inManagedObjectContext:self.managedObjectContext];
fetchRequest.entity = entity;
// Default sort descriptors are built in a separate custom method
NSArray *sortDescriptors = [self getDefaultSortDescriptorsForEntity:entity];
fetchRequest.sortDescriptors = sortDescriptors;
return [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"group.customers.firstName"
cacheName:nil];
Assuming this is a valid way to retrieve all notifications grouped by customer (I'm not sure about that either) iOS is throwing the following exception:
#"Failed to fetch all Notification objects"
#"Reason: Invalid to many relationship in setPropertiesToFetch: (group.customers.firstName) (NSInvalidArgumentException)"
I have reviewed the relationships again and again to see if something is missing and everything seem to be correct. I can create and delete objects for all entities and links between them are correct as well.
My question is: Is it possible to traverse through several relationships in a sectionNameKeyPath value? How should many-to-many relationships be handled in this kind of scenario?
Yes, you can do this. Just fetch the customers with the FRC and set the sectionNameKeyPath to nil.
Sections
The number of returned results is your number of sections. Populate the section headers with the customer data.
Rows
The number of rows in section would be customer.notifications.count. To populate the row, make sure the notifications are sorted in some way (say, by date) and display them accordingly with something like this:
NSArray *orderedNotifications =
[customerForSection.notifications sortedArrayUsingDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO]]];
Notification *notificationToBeDisplayed =
[orderedNotifications objectAtIndex:indexPath.row];
An alternative - recommended - solution is to change the data model. You could associate the notification with all customers directly. That would have the added advantage, that notifications remain associated with the correct customers even if group memberships change.

Navigating the Core Data Object Graph [2]

I asked a question yesterday where I really should have started with a simpler example. Having distilled my question to the basics, I've managed to solve my problem using existing SO questions and answers.
I'm summarising my question here (and providing my own solution) because I don't think there are any posts that explain this clearly enough. Being new to Core Data and struggling to get away from SQL concepts, I'd welcome feedback on how appropriate my solution is, and if there are better ways of modelling the problem.
Question
Given the following object model, that has three entities A, B and C, each linked by to-many relationships:
How is it possible to identify the parent A entities, that have grandchildren C entities with a particular attribute? By example, using these sample entities and their relationships:
How can I find our which entity A s have child entity C s with the Tag:Yes?
Solution
I've been able to achieve this is using the SUBQUERY keyword of NSPredicate. Here is the code snippet that worked for me (assuming you've set up your managed object context etc):
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityA" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:#"(0 != SUBQUERY(child, $a, (0 != SUBQUERY($a.child, $child, $child.tag == %#).#count)).#count)", #"YES"];
[fetchRequest setPredicate:predicateTemplate];
NSArray *test = [context executeFetchRequest:fetchRequest error:&error];
Breaking the all important NSPredicate string onto several lines:
(0 != SUBQUERY(child,
$a,
(0 != SUBQUERY($a.child,
$child,
$child.tag == %#).#count)
).#count
)
The important part is we are selecting the EntityA to work from and then in the nested subquery working through the child relationships of the entities. I expect this can be repeated for several depths. Not the most intuitive thing to put together... but it works. Comments welcome.
I've never tried to use a subquery before, that's very cool and good to see an example of it. You should add the solution as an answer to the question actually!
I would have probably done as Abizern suggested, since this is a tree-like hierarchy and its easier to traverse up the tree using to-one relationships.
In code this looks like:
NSManagedObjectContext *moc = APPDELEGATE.managedObjectContext;
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:[NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"tag = YES"]];
NSError *fetchError = nil;
NSArray *children = [moc executeFetchRequest:request error:&fetchError];
children is an array of EntityC objects that match the predicate. The next step is getting a set of unique EntityA objects that are the "grandparents" of these. We can take advantage of key-value coding here:
NSArray *grandParents = [children valueForKeyPath:#"parent.#distinctUnionOfObjects.parent"];
In this case, for efficiency, we'd probably want to prefetch the parent.parent keypath during our initial fetch request:
[request setRelationshipKeyPathsForPrefetching:#[#"parent.parent"]];
Hope this helps!
If you've followed the Core Data Guidelines and have set up reciprocal relationships you could try searching from the bottom up.
Fetch the Entity C objects that pass your required predicate and then get the Entity A object by going up the parents. No need for nested subqueries.
Edited to add
You can get the parent of C (which is an object of B) from the parent relationship. And from there you can get the parent (which is an object of A) form the parent relationship.
It's in your Core Data Model Diagram.

Resources