About Coredata "IN" & "NOT IN" - core-data

SQL Statement
SELECT * FROM TABLE WHERE ACTION IN ('A','B');
The following code is throwing an exception
- (NSArray*) fetchManagedObjects:(NSString*)className withFlag:(NSString*)flag{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:[self managedObjectContext]];
fetchRequest.entity = entity;
NSMutableString *str = [NSMutableString stringWithFormat:#" ( 1== 1 ) "];
if ([className isEqualToString:#"Entity3"]) {
[str appendFormat:#" AND ( kfollowAction IN ('%#') ) ", flag];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:str];
fetchRequest.predicate = predicate;
NSError *error = nil;
NSArray *entities = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return entities;
}
2013-06-20 16:45:51.318 CNPL[1447:1a903] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSCFString countByEnumeratingWithState:objects:count:]:
unrecognized selector sent to instance 0x16965b30'
* First throw call stack: (0x3647012 0x2ea2e7e 0x36d24bd 0x3636bbc 0x363694e 0x192966c 0x1928e06 0x191b829 0x191ad08 0x1915915 0x19155f2
0x19153c0 0x1915195 0x1914977 0x1914464 0x19135dd 0x1911539 0xdce02
0xe261a 0x1ec81c7 0x1ec8232 0x1ec84da 0x1edf8e5 0x1edf9cb 0x1edfc76
0x1edfd71 0x1ee089b 0x1ee0e93 0x1ee0a88 0xe1dd3 0x2eb6705 0x1dea2c0
0x1dea258 0x1eab021 0x1eab57f 0x1eaa6e8 0x1e19cef 0x1e19f02 0x1df7d4a
0x1de9698 0x3909df9 0x3909ad0 0x35bcbf5 0x35bc962 0x35edbb6 0x35ecf44
0x35ece1b 0x39087e3 0x3908668 0x1de6ffc 0x1ab1a 0x2f35)
How to use Coredata?

First of all: Never use stringWithFormat to build a predicate. The format specifiers
and quoting works differently in stringWithFormat and predicateWithFormat, so using string
formatting functions for predicates is very error-prone.
If you have to combine predicates at runtime,
use the methods from NSCompoundPredicate.
Now to select objects where the "kfollowAction" attribute has one of the values in a given list,
you have to use the "IN" operator with an array, for example:
NSArray *flags = #[#"A", #"B"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"kfollowAction IN %#", flags];
There is also no need for a dummy predicate "( 1== 1 )". If you want to fetch all objects,
then just don't set a predicate for the fetch request.

Related

Core Data - NSPredicate for objects with identical column and also meet another condition

I am looking for a predicate to fetch all managed objects of type Entity whose values are duplicated in a property sessionId, where all groups' ("groups", meaning managed objects whose sessionId's are equal) contents' flags in a property processed is set to YES. This can be done (slowly), but I am looking for an efficient one liner for this. Thanks
This is the slow way:
NSFetchRequest *request = [NSEntityDescription entityForName:#"Entity"
inManagedObjectContext:context];
NSArray *all = [context executeFetchRequest:request error:nil];
NSArray *sessionIds = [all valueForKeyPath:#"#distinctUnionOfObjects.sessionId"];
NSMutableArray *objects = [NSMutableArray array];
for (NSString *sessionId in sessionIds) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"sessionId == %#", sessionId];
NSArray *inSession = [all filteredArrayUsingPredicate:predicate];
for(id obj in inSession) {
if(![obj valueForKey:#"processed"]) continue;
}
[objects arrayByAddingObjectsFromArray:processed];
}
NSLog(#"%#", objects);
Since booleans are stored as 0s and 1s, a group where all rows have processed = YES will have average(processed) = 1. Hence you can use NSFetchRequest's propertiesToGroupBy and havingPredicate to get the sessionIds that meet your criteria. A second fetch is then required to get the Entity objects with any of those sessionIds:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = #[#"sessionId"];
fetch.propertiesToGroupBy = #[#"sessionId"];
fetch.havingPredicate = [NSPredicate predicateWithFormat: #"average:(processed) == 1"];
NSArray *resultsArray = [context executeFetchRequest:fetch error:nil];
NSArray *sessionIdArray = [resultsArray valueForKeyPath:#"sessionId"];
NSFetchRequest *newFetch = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
newFetch.predicate = [NSPredicate predicateWithFormat:#"name IN %#",sessionIdArray];
NSArray *finalResults = [context executeFetchRequest:newFetch error:nil];
NSLog(#"Final results, %#", finalResults);
Sorry it's not a one-liner. And I leave it to you to determine whether it's any quicker than your own code.
EDIT
To do it all in one fetch, use NSFetchRequestExpression in place of the intermediate arrays:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = #[#"sessionId"];
fetch.propertiesToGroupBy = #[#"sessionId"];
fetch.havingPredicate = [NSPredicate predicateWithFormat: #"average:(processed) == 1"];
NSExpression *fetchExpression = [NSFetchRequestExpression expressionForFetch:[NSExpression expressionForConstantValue:fetch] context:[NSExpression expressionForConstantValue:context] countOnly:false];
NSFetchRequest *newFetch = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
newFetch.predicate = [NSPredicate predicateWithFormat:#"sessionId IN %#",fetchExpression];
NSArray *finalResults = [context executeFetchRequest:newFetch error:nil];
NSLog(#"Final results, %#", finalResults);
Note that on my (admittedly trivial) test setup this actually ran more slowly than the two-fetch solution.
FYI, if you use the SQLDebug build setting to examine the SQL that is generated, it looks something like this:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZSESSIONID, t0.ZPROCESSED FROM ZENTITY t0 WHERE t0.ZSESSIONID IN (SELECT n1_t0.ZSESSIONID FROM ZENTITY n1_t0 GROUP BY n1_t0.ZSESSIONID HAVING avg( n1_t0.ZPROCESSED) = ? )

Core Data: Group by and count results returns empty list

Consider the following Core Data entity:
Person - personId: NSNumber, name: NSString, position: NSString
Using Core Data, I'm trying to replicate the following SQL query:
SELECT `position`, COUNT(*) FROM `Person` GROUP BY `position`
Below is the objective-c equivalent:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person"]
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: #"position"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:" arguments:#[keyPathExpression]];
NSAttributeDescription *positionDescription = [entity.attributesByName objectForKey:#"position"];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"count"];
[expressionDescription setExpression:countExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
[request setPropertiesToFetch:#[positionDescription, expressionDescription]];
[request setPropertiesToGroupBy:#[positionDescription]];
[request setResultType: NSDictionaryResultType];
NSError *error = nil;
NSArray *results = [context executeFetchRequest: request error: &error];
The Person entity is definitely populated, however, when executing the above code, the results array is empty. Thoughts?
(From my above comments:) A fetch request with NSDictionaryResultType returns only the objects from the store file, not the pending changes. Therefore you have to save the context to the persistent store before executing that fetch request.

fetch request for entity.attribute == #"somevalue"

How do I setup a fetch request to only pull the data from an entity's attribute with one particular value? This is the basic code I've used before.
-(void)fetchResults
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:self.entityDescription.name];
NSString *cacheName = [self.entityDescription.name stringByAppendingString:#"Cache"];
// predicate code
if (self.predicate != nil) {
[fetchRequest setPredicate:self.predicate];
}
// end of predicate code
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
BOOL success;
NSError *error;
success = [self.fetchedResultsController performFetch:&error];
if (!success)
{
NSLog(#"%#", [error localizedDescription]);
}
}
I've been looking at this page: http://bit.ly/KevYwR is this the right direction?
Do I need to use NSPredicate or can I do without?
Thanks for any help, point in the right direction, etc.
Setting up a NSFetchRequest is equivalent to a SELECT statetement in SQL language.
Here a simple example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:moc]];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
// error handling code
The array results contains all the managed objects contained within the sqlite file. If you want to grab a specific object (or more objects) you need to use a predicate with that request. For example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"attribute == %#", #"Some Value"];
[request setPredicate:predicate];
In this case results contains the objects where attribute is equal to Some Value. Setting a predicate is equal to put the WHERE clause in a SQL statement.
Note
I suppose that the name of the entity is EntityName and its property is called attribute which is of type string.
For further info I suggest you to read the Core Data programming guide and NSFecthRequest class reference.
http://developer.apple.com/library/iOS/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
Hope it helps.

(Core Data )Fetch an specific entity with a max property

I have a Entity wich has a toMany relationship to another one. In this second one I have an attribute called "versionNumber" so. I have an object on entity type A, and I want to get the related entity B which has the biggest (max) versionNumber.
I have the following but that returns me a result obtained over all records on entity B, not over the specific entities related to the object of type A.
NSInteger vNumber = 0;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:DPA_VERSION_KEY inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:VERSION_NUMBER_KEY];
NSExpression *maxNumberExpression = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"maxNumber"];
[expressionDescription setExpression:maxNumberExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [[self managedObjectContext] executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
vNumber = [[[objects objectAtIndex:0] valueForKey:#"maxNumber"] integerValue] +1;
}
}
[expressionDescription release];
[request release];
return vNumber;
I have an idea but I hadn't been able to materialize it. I must ask SELF which is my object A to do that fetch over its relationship toVersions (Entity B).
Thanks for the help.
G.
Set a predicate to limit the request to only those B objects who have a relationship with A.
[request setPredicate:[NSPredicate predicateWithFormat:#"myA == %#", myA];

Core Data: NSPredicate for many-to-many relationship. ("to-many key not allowed here")

I have two entities named "Category" and "Article" which have a many to many relationship. I want to form a predicate which searches for all articles where category.name is equal to some value. I have the following:
NSEntityDescription *entityArticle = [NSEntityDescription entityForName:#"Article" inManagedObjectContext:managedObjectContext];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"categories.name == [cd] %#", category.name];
[request setSortDescriptors:sortDescriptors];
[request setEntity:entityArticle];
[request setPredicate:predicate];
NSMutableArray *results = [[managedObjectContext executeFetchRequest:request error:nil] mutableCopy];
if ([results count] > 0)
NSLog(#"Results found.");
else
NSLog(#"NO results found.");
[request release];
[sortDescriptor release];
[sortDescriptors release];
The error I receive is *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
Are there any options to retrieve the desired data?
You're trying to compare a collection (categories.name) to a scalar value (category.name). You need to either use a collection comparator (CONTAINS), or use a predicate modifier (ANY/ALL/SOME, etc).
Try using:
[NSPredicate predicateWithFormat:#"ANY categories.name =[cd] %#", category.name];
Or:
[NSPredicate predicateWithFormat:#"categories.name CONTAINS[cd] %#", category.name];
SWIFT SYNTAX
In case anyone happens upon this writing in swift as I did...
let predicate = NSPredicate(format: "ANY categories.name = %#", category.name!)
fetchRequest.predicate = predicate
worked for me.
For those who faced the same problem. You can use IN operator instead of = / == to look for specific objects in any Collection.
Look here for reference. No difference Swift or Objective-C

Resources