Does CoreData on iPhone support IN predicates? - iphone

I'm trying to fetch a bunch of records of a certain type, based on a list of types defined by the user…
[fetchRequest setEntity:[NSEntityDescription entityForName:#"myRecord" inManagedObjectContext:self.managedObjectContext]];
NSSet *shipTypes = [NSSet setWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
NSPredicate *aPredicate = [NSPredicate predicateWithFormat:#"type in %#", shipTypes];
[fetchRequest setPredicate:aPredicate];
theRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
…when run, the executeFetchRequest message throws an exception…
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (type IN {71, 73, 70, 72, 74})'
Have I done something wrong, or is this really not supported?

I believe Alex is right that you have to use an NSArray, although obviously it'd be nicer if NSSet were accepted here, since order isn't that important (although it could conceivably affect how quickly the SQL can run).
As a side note, I never use the +predicateWithFormat: call in any code, ever, because it can't do compile-time sanity or type-checking. I highly advise using the individual classes.
In this case, I'd do:
fetchRequest.entity = [NSEntityDescription entityForName:#"myRecord" inManagedObjectContext:self.managedObjectContext]];
NSArray *shipTypes = [NSArray arrayWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
fetchRequest.predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:#"type"] rightExpression:[NSExpression expressionForConstantValue:shipTypes] modifier:NSDirectPredicateModifier type:NSInPredicateOperatorType options:0];
theRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
Not that this would have caught this particular error at compile time, but it WOULD have potentially caught it at the NSExpression level, thus making it much clearer what went wrong.

Hmm. I tried both adding the ANY keyword and using NSArray. Actually I was using NSAArray to start with when I got this exception from Core Data. In retrospect think that because I was combining two expressions with AND it was a problem for Core Data. (I know I should go back and verify this but this thought just occurred to me, while reading this post.) Here is my original expression:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"BPM BETWEEN { %d, %d } AND GENRE IN %#", lower, upper, genres];
My solution was to break it up into two parts. Now, I issue the query for the BPM using the predicate but I take the resulting array and use -filteredArrayUsingPredicate with #"genre IN %#". Thus:
array = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"genre IN %#", genres]];
This works for me.

I struggled with this. Eventually I ended up doing a sub predicate for each of the set.
So for example:
NSMutableArray *subpredicates = [NSMutableArray array];
NSArray *shipTypes = [NSArray arrayWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
for (NSNumber *num in shipTypes) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY type == %#", num]];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
That's assuming that type is a NSSet. If it's not you could just have:
for (NSNumber *num in shipTypes) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"type == %#", num]];
}

Try changing your predicate to #"ANY type IN %#"

Related

NSPredicate using OR error

First time building an NSPredicate.
I would like to search a managedobjectcontext using this logic:
Search for a, grab all matches
Search for b, grab all matches, etc....
Nsarray *results = (has all a results, b results, etc);
My attempted predicate is:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name== %# OR name == %# OR name == %#",a,b,c];
However I get errors with this predicate...
Edited: Sample method I wrote
-(NSPredicate*)parsePartsIntoAPredicate:(NSMutableArray*)inputPartsNames{
NSSet *keys=[NSSet setWithArray:inputPartsNames];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"any self.#name in %#", keys];
NSLog(#"predicate = %#",predicate);
return predicate;
}
Clarify: I have a database of cars (20,000) Each car has multiple parts. I want to find all cars that have part a, and all cars that have part b, and all that have part c. Then I want to return an array with cars with part a, b, c, etc...
If you think there is a better way let me know, but I am approaching this backwards. I am saying
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Cars" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[self parsePartsIntoAPredicate:inputParts]];
NSError *error;
NSArray *records = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
What am I doing wrong?
NSString *key;
NSMutableArray *tempArray;
NSPredicate *searchForName = [NSPredicate predicateWithFormat:#"name = %#", key];
NSArray *filterArray = [tempArray filteredArrayUsingPredicate:searchForName];
NSLog(#"%#",filterArray);
Where key is your searchKeyword, tempArray is your CompleteArray in which data is present.
Use Like this. Please put your data.
Use this
NSPredicate* predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%# '%#'", #"SELF contains[c] ",searchText]];
To fetch all Cars objects that have a name which is one of the strings in the keys set or array, use
[NSPredicate predicateWithFormat:#"name IN %#", keys]

why wont this NSPredicate work?

i have a list of objects being managed by CoreData. i want to get a specific object out of CoreData using an NSPredicate. below is the code i am using. Array arr always comes back with 0 objects in it presumably because the fetch cant find an object that matches the predicate. i know for a fact that at least 1 object in CoreData has an advertisement.uuid that matches adUdid. i have manually gotten the entire list of objects and searched it myself for the uuid and found it. advertisement is a member of WebServiceAuthService_mobileAdvertisementVO and uuid is a member of advertisement. whats even more aggregating is the fact that this exact code works just fine in another project. im at a loss to figure out why this code no longer works in the new project.
incase it matters this code is in a static library i am making.
EDIT: arr is always empty so there is nothing to post. there are also no errors being given. its just not working. the uuids are NSStrings something along the lines of "9ca98efe-ef48-47c0-aff5-058224b3093d". i have a feeling the problem may be elsewhere in the code and just manifesting itself here.
WebServiceAuthService_mobileAdvertisementVO *mobileAd = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"WebServiceAuthService_mobileAdvertisementVO" inManagedObjectContext:managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"advertisement.uuid == %#",adUdid];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *arr = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if (error)
{
DLog(#"fetched special ad error: %#",[error localizedDescription]);
}
if (arr && [arr count] >= 1)
{
DLog(#"found ad with UUID %#",adUdid);
for (WebServiceAuthService_mobileAdvertisementVO *obj in arr)
{
NSManagedObjectID *objID = [obj objectID];
if (![objID isTemporaryID])
{
mobileAd = obj;
}
}
}
You are comparing strings, in which case LIKE is a better operator than ==. So:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"advertisement.uuid LIKE %#",adUdid];
Don't worry about quotes, predicateWithFormat: will automatically put single quotes around the right hand term.
EDIT:
Reference: Predicate Programming Guide

Core Data performance issues

I have an iPhone app that has the needs to avoid inserting duplicate records, at first I thought I could just test each object in my in-memory array, create a fetch request for each object and test if it exists. But this is proving slow on the device, very slow (about 5 seconds, which is not acceptable).
I have been trying to piece together how to create some smart predicate that I could use in order to get this to work efficiently but without much success.
My objects have a NSNumber field that I have set as the "Identity Property" and also non-optional. This field is called sampleTime (again, this is NOT a date, but a NSNumber)
Here is my idea (borrowed from other threads and even some of my own questions of SO):
Obviously doing a fetch per object (around 380 objects) is not going to work for performance, so I was under the impression that I could do most of it in memory and it would be faster. I need to create some predicate that uses the IN clause, then iterate over that fetch result, testing if any one of the objects is inside that result set, if NOT then insert it, if SO then do nothing.
But my implementation is not working:
NSMutableArray *timeStampArray = [[NSMutableArray alloc] init];
for (id emTmp in myListOfObjects)
[timeStampArray addObject: [NSNumber numberWithInt:[[emTmp sampleTime] timeIntervalSince1970]]];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:[NSEntityDescription entityForName:#"ElectricalMeasurementEntity" inManagedObjectContext:context]];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"sampleTime in %#", timeStampArray]];
NSArray *results = [context executeFetchRequest:fetch error:nil];
int resCount = [results count];
But resCount is always zero, and I dont know why...
BTW, myListOfObjects contains business objects which also have a sampleTime property which IS an NSDate type.
EDIT
Ok, update, I got the basics working. The reason why I was not getting any results was because the loop that created the array was using the id type, which when used the way I was using it was not creating NSNumber objects correctly.
Now I do this:
for (id emTmp in myListOfObjects)
{
Measurement *t = (Measurement*)emTmp;
NSTimeInterval d = [t.sampleTime timeIntervalSince1970];
[timeStampArray addObject: [NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]];
}
which creates a nice list which works very well.
However, I then go on to do this:
NSMutableArray *itemsToInsert = [[NSMutableArray alloc] init];
for (id em in myListOfObjects)
{
BOOL found = NO;
Measurement *t = (Measurement*)em;
for (id e in results) //results from Fetch Request which is now populated properly
{
MeasurementEntity *entity = (MeasurementEntity*)e;
if ([entity.sampleTime isEqualToNumber:[NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]])
{
found = YES;
break;
}
}
if (!found)
[itemsToInsert addObject: t];
}
This loop (for around 850 objects, on the iPhone 3Gs) takes around 10 - 12 seconds, which I can see why (when 850*850 = 722500 loops!). Can I be more efficient about this?
Thanks
You need to strip the fetch down such that it will only check the one property. Then do all your comparisons with predicates for speed. Something like this:
NSArray *newData=[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:6],nil];
NSManagedObject *mo;
for (int i=0; i<5; i++) {
mo=[NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.moc];
[mo setValue:[NSNumber numberWithInt:i] forKey:#"numAttrib" ];
}
[self saveContext];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
NSEntityDescription *testEntity=[NSEntityDescription entityForName:#"Test" inManagedObjectContext:self.moc];
[fetch setEntity:testEntity];
// fetch only the one property you need to test
NSDictionary *propDict=[testEntity propertiesByName];
[fetch setPropertiesToFetch:[NSArray arrayWithObject:[propDict valueForKey:#"numAttrib"]]];
// Return as dictionaries so you don't have the overhead of live objects
[fetch setResultType:NSDictionaryResultType];
// fetch only those existing property values that match the new data
NSPredicate *p=[NSPredicate predicateWithFormat:#"numAttrib in %#",newData];
[fetch setPredicate:p];
NSArray *fetchReturn=[self performFetch:fetch];//<-- my custom boilerplate
// extract the existing values from the dictionaries into an array
NSArray *values=[fetchReturn valueForKey:#"numAttrib"];
// filter out all new data values that already exist in Core Data
p=[NSPredicate predicateWithFormat:#"NOT (SELF in %#)",values];
NSArray *unmatchedValues=[newData filteredArrayUsingPredicate:p];
NSLog(#"unmatcheValues=%#",unmatchedValues);
... which outputs:
unmatcheValues=(
6
)
Now you only need to create new managed objects for the values returned. All other new values already exist.
This may seem an obvious suggestion, but if [context executeFetchRequest:fetch error:nil] is not returning any results, seems like the first thing to do is check for errors instead of ignoring them (by setting error:nil).
Something like:
NSError *error = nil;
NSArray *results = [context executeFetchRequest:fetch error:&error];
if (results == nil) { // fetch failed - huh?
NSLog(#"Fetch error %#, %#", error, [error userInfo]);
}

How do I get Attributes from Core Data into an Array for - iPhone SDK

I'm trying to retrieve data from Core Data and put it into a Mutable Array
I have an Entity called 'Stock' and in Properties, attributes called : code, price & description...
How do I get the data stored in these attributes into a simple Mutable Array?
I've added this code...
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:[stock valueForKey:#"code"]];
and I get this error...
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I have a 'Managed Object Class' called 'Stock' and declared called stock. Am I missing something?
If I do this in the -cellForRowAtIndexPath...
Stock *stock1 = [fetchedResultsController objectAtIndexPath:indexPath];
array = [[NSMutableArray alloc] init];
[array addObject:stock1.code];
NSLog(#"Filtered List is? %#", array);
In the console I can see these 2 items
'The Filtered array is 810005'
'The Filtered array is 810007
'
What must I do to get these items(810005 & 810007) into an array set up in the -viewDidLoad method? Like it does in the -cellForRowAtIndexPath?
Update
Hi Marcus,
Finally got it working (well, 80%)
I put this in the -cellForRowAtIndexPath
Stock *product = nil;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
filteredListContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
product = [self.filteredListContent objectAtIndex:indexPath.row];
[self configureFilteredCell:cell atIndexPath:indexPath];
[filteredListContent objectAtIndex:indexPath.row];
NSLog(#"Filtered List Array List is? %#", stock1.code);
}
else
{
listContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
[self configureCell:cell atIndexPath:indexPath];
NSLog(#"List Array List is? %#", stock1.code);
}
Then I used this code in the scope
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
self.savedSearchTerm = searchText;
if (searchText !=nil)
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code beginsWith[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}
else
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code contains[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
[self.tableView reloadData];
}
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error])
{
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
Everything is filtering fine but when I hit cancel on the search, it's not reloading the original data...
I won't be defeated...!!
Thanx
Since you are having this issue in your -viewDidLoad, I am guessing (and without the code from -viewDidLoad, it is only a guess) that you are trying to fetch objects from the NSFetchedResultsController before the -executeFetch: has been called on the controller and therefore you are in the land of nils.
I would suggest setting a break point in your -viewDidLoad and watching the values and you walk through your code. This will tell you what is nil and where.
Of course a better question is, why are you trying to put NSManagedObject instances into a NSMutableArray? Since they are already in your NSFetchedResultsController is there really a need to build up another array? What is the end goal?
Update
Now I understand what you are trying to do.
Solution 1
Only populate the array when a search has been conducted. Take a look at the http://developer.apple.com/iphone/library/samplecode/TableSearch/index.html example code and you should see how to apply it to your situation.
If you want to enter the table view with a pre-defined search then you need to perform it after you have executed a -performFetch: in the NSFetchedResultsController.
Solution 2
Modify the NSPredicate on the NSFetchedResultsController to include your search terms and then execute -performFetch: on the NSFetchedResultsController, you may have to do a -reloadData on the table as well, I am not sure.
When the user clears the search field you reset the predicate and re-fetch everything. Since it is all cached there should be no performance penalty.
Solution 2 just occurred to me and I have not tested it personally but there is no reason it shouldn't work just fine. Should even give you live updates within the search.
Have you read the documentation? You fetch your Stock instances (all of them or filter them with a predicate), then do with them whatever you please.
You can then add their properties to an array individually:
[array addObject:[stockInstance valueForKey:#"price"];
... or use a combination of < NSKeyValueCoding > protocol methods such as -dictionaryWithValuesForKeys: NSDictionary methods such as -objectsForKeys:notFoundMarker: to get an array for given keys.
This may or may not actually be what you need to do, though. It depends on what you intend to use the resulting array for. If you want a quick sum of all matching Stock instances' "price" values, for example, you can use Set and Array Operators. It really depends on what you're trying to achieve.
When I got your error,
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I had given the fetchedRequest a sort descriptor that had a nil key. The error appeared when I used these lines:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
The error disappeared when I set the key to #"name":
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

NSPredicate aggregate IN statement not working

I am a bit of a CoreData noob but am slowly getting my head around it. I am having trouble with the following code:
NSArray * artisteIds = [#"1,2,3,4" componentsSeparatedByString:#","];
predicate = [NSPredicate predicateWithFormat:#"(artisteId IN %#)", artisteIds];
My Artiste managed object has a NSNumber field of artisteId and I have looped through all Artistes in my object context and there are definately objects with id's 1-420.
But my helper method always returns a empty result set with that query
NSMutableArray* mutableFetchArtistes = [CoreDataHelper searchObjectsInContext:#"Artiste" :predicate :#"title" :YES :managedObjectContext];
Any suggestions?
To test it works I used the following and got a count of 3 results
predicate = [NSPredicate predicateWithFormat:#"(artisteId = 1) or (artisteId = 2) or (artisteId = 3)", artisteIds];
I think the array artisteIds should contain NSNumber objects, e.g.
NSArray *artisteIds = [NSArray arrayWithObjects:
[NSNumber numberWithInteger:1],
[NSNumber numberWithInteger:2],
[NSNumber numberWithInteger:3],
[NSNumber numberWithInteger:4],
nil];
You could also use NSSet rather than NSArray, but I'm not sure if it makes a difference.