I've been trying to see if there is any way I can improve on the performance of the following Predicate:
[NSPredicate predicateWithFormat:#"A=%# and SUBQUERY($self.words,$k,$k.keyword IN %#).#count==%d",
<value for A>,
keywordList,
[keywordList count]];
What I'm trying to do is return all the records of A that have keywords that are ALL contained in the supplied array (keywordList). I have a small database of about 2000 records. However, the keywords for each Entity ranges from 40-300 keywords. The keywords are represented as a to-Many relationship from A to an entity called Keywords. The above query works but takes about 7 seconds to run on my iPhone4. I want to see what I can do to shrink that down to a sub-second response.
Thanks,
Michael
This doesn't quite solve the problem as the results that come back from intersection of the fetchedObjs is not correct. It doesn't guarantee that all the 'A' objects contain all the supplied keywords. In fact, what comes back is an empty list and the entire process actually takes longer.
Something must be wrong with my model or the previous answer. I'm not getting the results I'm expecting. One alternative to returning a list of 'A' would be to return a list of ManagedObjectIDs of 'A'. [mo valueForKey: (NSString *)] returns the object of the relationship.
Note: I originally posted this question anonymously thinking I was logged in, so i can't seem to comment on anybody's answer.
I think you are approaching the problem backwards. If you have the keywords, you should search for the Keyword objects and then walk their A relationships to find the related A objects. Then check for overlap in the relationship sets.
So something like:
NSFetchRequest *fetch=//...set up fetch
[fetch setEntity:Keyword_entity];
NSPredicate *p=[NSPredicate predicateWithFormat:#"keyword IN %#",keywords];
[fetch setPredicate:p];
NSArray *fetchedObj=//... execute fetch
NSMutableSet *inCommon=[NSMutableSet setWithCapacity:[fetchedObjs count]];
for (NSManagedObject *mo in fetchedObjs){
if ([inCommon count]==0){
[inCommon addObjects:[mo valueForKey:#"aRelationshipName"]];
}else{
[inCommon intersectSet:[mo valueForKey:#"aRelationshipName"]];
}
}
... inCommon would then contain a set of all unique A objects that shared all the keywords.
Update:
From OP author:
This doesn't quite solve the problem
as the results that come back from
intersection of the fetchedObjs is not
correct. It doesn't guarantee that all
the 'A' objects contain all the
supplied keywords.
Okay, lets take another tack. Assuming you have a data model that looks something like this:
A {
keywords<<-->>Keyword.as
}
Keyword{
keyword:string
as<<-->>A.keywords
}
Then something like this should work:
NSFetchRequest *keyWordFetch=//...set up fetch
[keyWordFetch setEntity:Keyword_entity];
NSPredicate *p=[NSPredicate predicateWithFormat:#"keyword IN %#",keywords];
[keyWordFetch setPredicate:p];
NSArray *fetchedKeywords=//... execute keyWordFetch
NSFetchRequest *entityAFetch=//...set up fetch
[entityAFetch setEntity:A_entity];
NSPredicate *pred=[NSPredicate predicateWithFormat:#"ALL keywords IN %#", fetchedKeywords);
[entityAFetch setPredicate:pred];
NSArray *fetchedAs=//... execute fetch
Related
I am having some trouble writing a predicate for my NSFetchedResultsController to get all Modifier from a table where where Exercise and Modifier contain the same Exercise_Modifier
The model look like this: Exercise <->>>Exercise_Modifier<<<->Modifier
I've tried using ANY and ALL keywords but cannot seem to get to the answer. I've tried this along the line of this:
fetchRequest.predicate = NSPredicate(format: "ALL exercise_modifiers == %#", exercise!.modifiers!)
where exercise_modifiers is the relationship from Modifier <->>>Exercise_Modifier and exercise!.modifiers! are the Exercise_Modifier for the current exercise I have. Does anyone know where I might be missing something?
I am fairly new to core data. I have database which contains articles with different categories. I want to fetch data from multiple categories, which can be selected by user on the fly. Now I have NSArray which contains selected multiple categories (count from 1 to n) I want to add predicate for selected categories but I'm not able to do so. as per my knowledge i have added for loop for adding categories in predicate but it is not working.
for i in 0...(catArray?.count)!-1 {
let str = String(format:"catId = %#",(catArray?.object(at: i) as? NSNumber)!)
predicateString = predicateString+str
}
let dP = NSPredicate(format:"%#",predicateString)
fetchedRequest.predicate=dP;
but app is crashing while fetching request.
Is there any other way to do it?
Please help me with this.
Any help is much appreciated.
To select objects which have any category from the given array
a simple predicate is sufficient:
NSPredicate(format: "catId IN %#", catArray)
Remark: Never use String(format:) to build predicates dynamically,
because almost surely the quoting and escaping will be wrong.
Use only NSPredicate(format:) and, if necessary, NSCompoundPredicate.
Currently I am using NSPredicate to search from a string. But I have around 2000 records. So iterating through these records for search using NSPredicate take some time.
I just want to know whether NSPredicate using any search algorithm internally or should I use any search algorithm for fast searching ?
I want my app should search like contacts app (smoothly). What should I do so that my search will be smooth like contacts app.
Thanks.
NSPredicate doesn't use any search algorithm - all NSPredicate does is represent your query.
It's the job of whatever accepts NSPredicate as a option to choose the search algorithm. In your case, it's probably NSArray's filteredArrayUsingPredicate: method?
If you want to search the data faster, you will have to write a better algorithm yourself, I bet the array just runs through each item and matches it to the predicate. Not efficient but it works for anything :)
Try looking at sqlite (or CoreData), they have better indexing than plain arrays.
Or you could make an index yourself - put each contact into a dictionary with the key being the first letter of the contact - that should reduce the amount of records you have to search!
i.e. instead of an array
[ #"Alex", #"Bob", #"Bill", #"Carl" ... ]
Have a dictionary
{
#"A" : [ #"Alex" ],
#"B" : [ #"Bob", #"Bill" ],
#"C" : [ #"Carl" ]
...
}
For the search string #"Bi" you only have to search through the items in the array for #"B".
PS Don't forget to make your dictionary key case / diacritic independent ;)
Based on the data model here: Photographer<------->>Photo
When the user goes to add a Photo, the user also specifies what Photographer took said picture. When the user decides to save the Photo, there is the possibility that the Photographer doesn't exist yet. After executing a NSFetchRequest to get a list of all instances of Photographer, how do I check if the NSMutableArray(photographerArray) contains an object that has the same fullName attribute as what the user is currently adding?
The naive way would be to simply get all the fullName of the array and check if it's in it.
BOOL photographerExist = [[listOfPhotographer valueForKey:#"fullName"] containsObject:enteredFullName];
However, it would be way more easier to put that directly in your fetch request. Just add a predicate to it.
NSPredicate *fullNamePredicate = [NSPredicate predicateWithFormat:#"fullName = %#", enteredFullName];
fetchRequest.predicate = fullNamePredicate;
If the result of the fetch is empty, then the photographer doesn't exist yet.
here is my core data model:
Locations < --- >> ThemesList << --- > Themes
The entites have the following attributes:
Locations
- Property: Name
- Relationship: ThemesList
ThemesList
- Relationship: Locations
- Relationship: Themes
Themes
- Property: Name
- Relationship: Locations
I am running a fetch on entity Locations and I want to only pull values where the name property in Themes is equal to a particular value. Based on what I've read, I need to do a subquery. I've tried something along the lines of the code below, but I always receive the error of Unable to parse the format string
[NSString stringWithFormat:#"SUBQUERY(ThemesList, $theThemes, $theThemes.Themes.Name LIKE %#)", #"a theme name"];
Any ideas on how I can accomplish this - what am I doing wrong?
Thanks
As a rule of thumb, you should fetch the objects of the entity you are testing with the predicate instead of starting with another entity altogether and walking a relationship inside the predicate. Walking relationships, especially to-many relationships inside of predicates is computationally intensive and slow.
Instead, I would recommend running the fetch against the Theme entity and then just ask the returned Theme objects for their associated Locations objects.
Your predicate would simplify to just:
NSPredicate *p = [NSPredicate predicateWithFormat:#"Themes.Name LIKE %#", #"a theme name"];
Then use a collection operator to find the location objects:
NSSet *locations=[fetchedThemes valueForKeyPath:#"#distinctUnionOfSets.locations"];
Your problem is this: $theThemes.Themes.Name. The parser will want to parse it as a variable expression (since it starts with a $), but when it comes across the ., it's going to produce an error.
As it turns out, the SUBQUERY is unnecessary. That same query can be effectively done as:
NSPredicate *p = [NSPredicate predicateWithFormat:#"ANY ThemesList.Themes.Name LIKE %#", #"a theme name"];
Hopefully Core Data will have an easier time handling that than the SUBQUERY.