Core Data - Select distinct - ios

I know there have been several discussions about this but none of them resolved my simple problem.
I have an Entity called Character and inside there are 4 columns:
character_id, episode_id, title, desc
there can be several same character_ids values but with different episode_id.
When I perform fetch\select I do it for whole table and wishes to get it distinctly by character_id. so this is what I do:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
// Add a sort descriptor. Mandatory.
if(sortDescriptors != nil) {
[fetchRequest setSortDescriptors:sortDescriptors];
}
fetchRequest.predicate = predicate;
// Required! Unless you set the resultType to NSDictionaryResultType, distinct can't work.
// All objects in the backing store are implicitly distinct, but two dictionaries can be duplicates.
// Since you only want distinct names, only ask for the 'name' property.
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = [NSArray arrayWithObject:[[entity propertiesByName] objectForKey:#"title"]];
fetchRequest.returnsDistinctResults = YES;
NSArray *fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
The 'fetchResults' array contains 3 out of 10 rows which is the right result!
The problem: None of the object within the array is accessible.
If I try the following:
NSDictionary item1 = [fetchResults objectAtIndex:0];
NSString *title = [item1 objectForKey:#title"];
I get an exception!
What am I doing wrong?? how can I translate back the dictionary into NSManagedObjects??
Thank you!

First, when using Core Data you should not use foreign keys. Rather, it is preferable to use Core Data's relationships and let the framework deal with the ids in an opaque manner. Maybe you are synching with a web service and need to keep track of the ids but your program logic actually should not depend on it.
Second, if you need an object, it is really better to use the NSManagedObjectResultType rather than the NSDictionaryResultType. You can still obtain distinct results. If you are not experiencing performance issues, this is the preferred pattern. The code is also much more readable.
Your data structure would be this, with a many-to-many relationship:
Character <<--->> Episode
All characters of an episode or all episodes with a certain character is simple. These will be "distinct" results dictated by the logic of the data model:
NSArray *allCharactersInEpisode = episode.characters;
NSArray *allEpisodesWithCharacter = character.episodes;
To select all characters of all episodes you just select all characters. Much simpler than a "distinct" query.

Related

Core Data Sorted relationship + NSFetchedResultsController

I have a model with Tags and Documents. Each Tag can have multiple Documents. Each document can have multiple Tags. In a Tag all the Documents are ordered. The Tag.Documents relationship is currently an NSSet, so it's unordered.
I am also using a NSFetchedResultsController with a NSSortDescriptor that just returns the Documents in other of their creation-time property. But I need them to be in a different order depending on the Tag I'm currently displaying.
Having looked at multiple other SO questions it seems that using a NSOrderedSet might not offer the solution, so instead I am trying to add a third object TagDocumentPair that points to 1 Tag and 1 Document. Each tag and document will point to multiple TagDocumentPair objects. Pretty straight forward.
The question I have is how should I create the NSSortDescriptor?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Document" inManagedObjectContext:.managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"tags contains %#",tag];
request.fetchBatchSize = 20;
request.sortDescriptors =
// #[[NSSortDescriptor sortDescriptorWithKey:#"creationTime" ascending:NO]];
This would probably be easy to do with a JOIN...
You are right that a fetch request cannot sort the results according to an ordered relationship, so using an "intermediate" entity might be the only solution.
You would then execute a fetch request on the "TagDocumentPair" entity instead of the "Document" entity:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"TagDocumentPair" inManagedObjectContext:managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"tag == %#", selectedTag];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"orderNumber" ascending:NO]];
where "orderNumber" is an attribute of "TagDocumentPair" that describes the ordering
of the documents of a tag.
Since each "TagDocumentPair" points to exactly one "Document", you can display the
document attributes in cellForRowAtIndexPath etc.
A disadvantage of this solution might be that changes to the attributes of a "Document" will not automatically trigger table view updates, because a fetched results controller does
not track changes to related objects.

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

Multiple NSSortDescriptor Not Working When The 1st Descriptor is NSDate

I am trying to separate the data into sections by 'recordDate' then for each section the data will be sorted by 'elementName' in ascending order. Following is the current code (not working):
NSManagedObjectContext *context = self.tankDatabase.managedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Log"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"recordDate" ascending:NO], [NSSortDescriptor sortDescriptorWithKey:#"elementName" ascending:YES selector:#selector(localizedStandardCompare:)], nil];
request.predicate = [NSPredicate predicateWithFormat:#"ownedBy = %#", self.tank];
self.controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:#"recordDate"
cacheName:nil];
self.controller.delegate = self;
NSError *error;
BOOL success = [self.controller performFetch:&error];
However when I tried to use other properties to sort (for example: elementName ASC, value DESC and group them by elementName) it does work as it should. Please note that the recordDate is a 'Date' type in data model and NSDate in the class.
Also the recordDate contains minute & seconds where it needs to be grouped to that detail.
I've tried to search the entire internet for similar case but I haven't found any solutions that work. What do I miss in my code? Or is it just an Apple bug? Thank you for your time and assistance.
I finally figured it out:
Apparently it's the sub-seconds information that also being saved into Core Data making the comparison between dates (even though the dates being compared share the exact same date) not working.
So what I did was to remove the sub-seconds information before saving it to Core Data:
self.recordDate = [NSDate dateWithTimeIntervalSinceReferenceDate:floor([recordDate timeIntervalSinceReferenceDate])];
I hope this can help anyone facing the same problem. Cheers!
Yes, if you want your subsorting descriptor (based on elementName) to kick in, then you need some ties with respect to recordDate, so your idea of truncating recordDate is good. In fact, many a time, NSDate information is truncated to YY:MM:DD so all events belonging to the same day are indistinguishable.
As an aside, often only your sectionNameKeyPath needs to be "coarse grained", e.g., you make a new transient attribute by removing the subsecond information from recordDate, and feed THAT to NSFetchedResultsController as sectionNameKeyPath. In other words, you can continue with recordDate as it is...
This would be similar to Last Name sorting (Ascending, say) where the section names are A, B, C, ..., Z.

Core Data, display to-many relationship distinctly along with aggregate count

I'm beginner in Core Data, so trying to wrap my mind around the following.
Let's say I have the following model:
Kid (1) -> (M) ToyName (car)
ToyAttributes (1) -> (M) key (color), value (black)
key (price), value (20)
key (store), value (toys r us)
ToyAttributes has 3 key/value pairs.
What I want to do is list the colors distinctly. So, if other toys are black also, I want to display black only once.
Another nice thing to do would be to display the aggregate count of how many black toys we have.
I currently have the following:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Kid" inManagedObjectContext:managedObjectContext_];
But unsure how to structure the proper KVC to find the proper key=color and get those values for every kid.
You need to set inverse relationship on your Entity, then if you use an NSPredicate to fetch the black one you will be able to follow the inverse relationship to retrieve the desired ToyName.
You minimally want to read those :
Fetching Managed Objects
Predicate Format String Syntax
This is some old code I've got, not perfect and not exactly what you are looking for, but still an example of the use of a NSPredicate.
This is a simple NSFetchRequest that will return all kListe entity ordered in some way.
- (NSFetchRequest *)requestPourListe{
NSFetchRequest *resultat = nil;
NSEntityDescription *description4Liste =
[NSEntityDescription entityForName:kListe
inManagedObjectContext:self.managedObjectContext];
resultat = [[NSFetchRequest alloc] init];
[resultat setEntity:description4Liste];
[resultat setSortDescriptors:self.sortDescriptorsPourNom];
// Si on veut faire un Fetch plus petit en mémoire (Partial Fault)
//[resultat setPropertiesToFetch:[NSArray arrayWithObject:kNom]];
return [resultat autorelease];
}
// Now time to use that request get the request
NSFetchRequest *requet4Liste = FRUtile.requestPourListe;
NSError *monErreur = nil;
// And Use it agains a NSManagedObjecContext (moc)
NSArray *lesListes = [moc executeFetchRequest:requet4Liste
error:&monErreur];
In the folloing self.list is one of the NSManagedObject that was fetch with the preceding request.
// those NSSet are set that were return from a simple relationship query
// so those set contain NSManagedObject
NSSet *desListeItems = self.liste.listeItems;
// HERE I'm fetching the relationship kRelationPrixElement on all NSManagedObject
// present in the set desListeItems (one to one relationShip)
NSSet *desPrixElements = [desListeItems valueForKey:kRelationPrixElement];
// generate the path, traverse the relationship call kRelationCommerce and
// reach it's attribute name kNom
NSString *predicateKey = [NSString stringWithFormat:#"%#.%#", kRelationCommerce, kNom];
// get an array out of one set probably not necessary
NSArray *desPrixElementsAR = [desPrixElements allObjects];
NSMutableArray *lesPrixAAfficher = [[NSMutableArray alloc] initWithCapacity:0];
for (NSString *value in self.commercesNoms) {
// in a predicate with format %K MUST be the key or keyPath and
// %# MUST be the value you are searching for
// the predicate here is done agains a many-to-one relation
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(%K MATCHES %#)", predicateKey, value];
// Now I'm applying the predicate to the array
[lesPrixAAfficher addObjectsFromArray:[desPrixElementsAR filteredArrayUsingPredicate:predicate]];
}
There is a thing that you must take into account while designing your model and your fetch request, you can follow relationship in a keyPath throughout many Entities AS LONG AS those relationship are to one relationship.
You can't use a keyPath to traverse a to-many relation, because how could Core Data know which of the entity it should follow on.
And finally when you got an NSManagedObject you can use dot notation on it to retreive it's properties and relationships.

iOS Core Data unique object fetch

I'm fairly new to iOS/Cocoa and I have a very fundamental question about core data. I was looking over the internet to find a appropriate answer/solution but I wasn't able to.
So the question is how do you handle uniqueness in core data? I know that core data is not a database, its something like an object graph. Lets assume we have an entity called 'User' with the attributes 'id' and 'name' and a few relations to the other entities. Now we want to update the name of a specific user (e.g. a web service gave us the id and the new name).
This was the way I have done that before:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"id == %#", anId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *user = [results lastObject];
But than I've heard this is bad practice. Is it because fetch requests with predicates are very slow? I can't imagine that this is such a big deal. As far as I know there is no other way to get a unique object rather than go over each object and checking for equality..
Would it be more efficient to fetch all objects of the entity, put them in an array and looping through it manually (instead of the fetch request)?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *resultUser;
for(User *user in results){
if([user.id isEqual:anId]) resultUser = user;
}
Please help me finding the right path and thanks.
If you have an index for the property that you're fetching (there's a checkbox in the model editor), it's definitely a lot more efficient to use an equality predicate for fetching. Actually, it's never more efficient to fetch all objects and iterate over them in memory, but an index makes the difference more significant.
You're not fetching a unique object but rather objects containing a notionally unique atrribute value.
All managed objects are unique instances but nothing in Core Data enforces that the attribute values are unique. You could in principle have an arbitrary number of unique managed objects all which had identical attribute values. Only relationships enforce a unique position in the object-graph.
There's no particular reason not to fetch a particular object that contains a particular value if that is what your app requires.
I think what you've read is warnings against trying to cram SQL-like key values into entities and then to try and link managed objects together with those keys using predicates, for example doing something like:
EntityOne{
sqlKey:string
}
EntityTwo{
sqlKey:string
}
… and then trying to relate objects of the two entities with predicates.
(anEntityOneObject.)sqlKey == anEntityTwoObject.sqlKey
… instead of just setting a relationship in the data model.
EntityOne{
two<-->EntityTwo.entityOne
}
EntityTwo{
one<-->EntityOne.two
}
… and just finding the related objects with AnEntityOneObj.two.

Resources