Core data - nested sum with where condition - ios

I have a application that show the amount sold by a store all the time.
NSNumber *amount = [self valueForKeyPath:#"sales.#sum.value"];
Now i have to filter and show the amount by period. I have the field "period" (string) that holds month and year, like "2012-11".
How can i make the same query using the where clause?

You can filter the sales before computing the sum.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"period == %#", #"2012-11"];
NSSet *sales = [self valueForKeyPath:#"sales"];
NSSet *filteredSales = [sales filteredSetUsingPredicate:predicate];
NSNumber *amount = [filteredSales valueForKeyPath:#"#sum.value"];
But when you have many dozens or more sales, this method is not really efficient because it requires to load each sale into memory.
Moreover, if the sales are in fault, each sale will fire a fault and generate a SQL request. You may prevent this by batch faulting the sales before filtering them.
We will assume that self is a managed object.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"period == %#", #"2012-11"];
NSSet *sales = [self valueForKeyPath:#"sales"];
// Begin batch-faulting
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:[NSEntityDescription entityForName:#"Sale" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"self IN %#", sales]];
[request setReturnsObjectsAsFaults:NO];
[moc executeFetchRequest:request error:NULL];
// Batch-faulting done
NSSet *filteredSales = [sales filteredSetUsingPredicate:predicate];
NSNumber *amount = [filteredSales valueForKeyPath:#"#sum.value"];
However, you still get several allocations for each sale (the managed object, its cache, its attributes). The best solution is to use a fetch request to compute the sum in the SQLite database.
NSString *reverseRelationship = #"store"; // name of the relationship from the sale to self
NSExpressionDescription *description = [NSExpressionDescription new];
[description setName:#"amount"];
[description setExpressionResultType:NSDoubleAttributeType];
[description setExpression:[NSExpression expressionWithFormat:#"#sum.value"]];
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:[NSEntityDescription entityForName:#"Sale" inManagedObjectContext:moc]];
[request setResultType:NSDictionaryResultType];
[request setPredicate:[NSPredicate predicateWithFormat:
#"%K == %# AND period == %#", reverseRelationship, self, #"2012-11"]];
[request setPropertiesToFetch:#[description]];
NSDictionary *result = [[moc executeFetchRequest:request error:NULL] lastObject];
NSNumber *amount = [result objectForKey:#"amount"];

Related

Filter title from core data

I have an entity that has title title and amount. I would like to display the total value I have for each title in my entity.
You can pretty much dispense with fetch requests by leveraging KVC (Key-Value-Coding).
First fetch (or filter) with this predicate:
[NSPredicate predicateWithFormat:#"category = %#", #"Food"];
Then just sum up the result in one line:
NSNumber *sum = [result valueForKeyPath:#"#sum.amount"];
There is no need to fetch only certain attributes using NSDictionaryResultType - just fetch the normal NSManagedObjects (Core Data will optimize for you).
You use NSPredicate to filter your objects by category and then you can fetch only the values of a certain property e.g.:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// Entity
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Incoming" inManagedObjectContext:context];
[request setEntity:ent];
// Predicate
NSPredicate *pred = [NSPredicate predicateWithFormat:#"category MATCHES[cd] %#", #"FOOD"];
[request setPredicate:pred];
// Property fetch
[request setResultType:NSDictionaryResultType];
[request setReturnsDistinctResults:NO];
[request setPropertiesToFetch:#[#"Amount"]];
// Execute the fetch.
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
int total = 0;
if (objects) {
for(NSDictionary *dict in objects) {
NSString *key = dict.allKeys.lastObject;
total += [dict[key] intValue];
}
} else {
NSLog(#"Fetch error: %#", error.localizedDescription);
}
NSLog(#"Total: %i", total);
The following will get the categories and the sum in one fetch operation:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Incoming"];
fetchRequest.resultType = NSDictionaryResultType;
NSExpression *sumExp = [NSExpression expressionWithFormat:#"sum:(amount)"];
NSExpressionDescription *sumED = [[NSExpressionDescription alloc] init];
sumED.name = #"sum";
sumED.expression = sumExp;
sumED.expressionResultType = NSDoubleAttributeType;
fetchRequest.propertiesToFetch = [#"title", sumED];
fetchRequest.propertiesToGroupBy = [#"title"];
NSArray *dictionaries = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
See the NSExpression documentation here.

Subquery Predicate Core Data

I have two Entity models "Content" and "LessonsContent" with no relationship between them. Both entities have common attribute "contentID" of NSNumber type. I am fetching all contents objects from "Content" entity for given predicate and storing these objects in contents array. I was successful in doing so.
Now, I want to fetch all lessonsContent objects from "LessonsContent" entity whose contentIds are present in contents array.
I am using following code:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"(contentType == %#)", #"Games"];
NSError * error;
NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Content class])];
[request setPredicate:predicate];
[request setReturnsObjectsAsFaults:NO];
NSArray *contents = [[DELEGATE managedObjectContext] executeFetchRequest:request error:&error];
if(error){ NSLog(#"Error in fatching data");}
//predicate = [NSPredicate predicateWithFormat:#"Content.contentID IN %#", contents];
predicate = [NSPredicate predicateWithFormat:#"(SUBQUERY(contents, $x, $x.Content.contentID IN %#).#count != 0)",contents];
request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([LessonsContent class])];
[request setPredicate:predicate];
NSArray * mappingArray = [[DELEGATE managedObjectContext] executeFetchRequest:request error:&error];
NSLog(#"mappingArray %#", mappingArray);
But app is crashing. Please suggest appropriate solution.
Thanks in advance.
You have to get an array of contentID's first. Then use IN.
NSArray *contents = [[DELEGATE managedObjectContext] executeFetchRequest:request error:&error];
NSArray * allContentIDs = [contents valueForKey:#"contentID"];
then use the second predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"contentID IN %#", allContentIDs];
[request setPredicate:predicate];
Hope this helps.

Retrieving all the NSSet element values in a one to many relationship

I have a one to many relationship as shown in the attached image. I want to list down all the course titles a particular lecturer teaches and the solution i have come up so far is as follows.
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Lecturer"];
NSMutableSet *courseList = [[NSMutableSet alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == 'smith'"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *lecturers = [self.managedObjectContext executeFetchRequest:request
error:&error];
for (Lecturer *lecturer in lecturers)
{
NSSet *set = lecturer.courses;
for (Course *course in set)
{
[courseList addObject:course.title];
}
}
The courseList object contains all the courses a given lecturer teaches.
IS there a better way to do this using nspredicates alone and not by using 2 for loops ?
Thanks in advance.
You can just fetch the Course objects directly, but specifying a different predicate that requires their authorname.name to match:
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Course"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"authorname.name == 'smith'"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *courseList = [self.managedObjectContext executeFetchRequest:request error:&error];
If you want an array with just the titles, you can then use:
NSArray *courseTitles = [courseList valueForKeyPath:#"title"];

Strange NSPredicate Behaviour?

Currently, I have a table view which is populated by video objects depending on the date they were recorded which was taken by using [nsdate date]; when they were recorded and saved. However, it is possible to record multiple videos in one day. I sort them by the date, and when there is more than one video with the same date, it displays the first one recorded on that date, with the most recent ones following in ascending order according to the most recent. I'm not sure why this happens but would like to check if there are more than one videos with the same date recorded, it organizes them by title. In SQL I could do something like:
ORDER BY DATE_RECORDED, TITLE ASC
Here is what I am currently doing:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"whosVideo == %#", _currentPerson];
[request setPredicate:predicate];
NSEntityDescription *eval = [NSEntityDescription entityForName:#"Video" inManagedObjectContext:_managedObjectContext];
[request setEntity:eval];
NSSortDescriptor *sortDescriptor =
[[NSSortDescriptor alloc] initWithKey:#"date_recorded"
ascending:NO
selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil){
//handle error
}
[self setVideoArray:mutableFetchResults];
[self.tableView reloadData];
How would I handle adding a second predicate if there is more than one object recorded on the same date?
If you want to handle more than one predicate than you have to use NSCompoundPredicate try like this below:-
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"whosVideo == %#", _currentPerson];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"whosVideo == %#", _someOther];
NSArray *subPredicates = [[NSArray alloc] initWithObjects:predicate1, predicate2,nil];
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];

Core Data fetch request optimization

I'm developing and application which needs to check if a string has been saved to the database or not. This may seem an easy operation but it needs half a second to return any response which I think is quite a lot. My question is if there is any way to reduce that time. Thanks for your interest.
This is my current code:
- (BOOL) isDeleted:(int)i {
NSString *value = [NSString stringWithFormat:#"deleted.*.%i", i];
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSString *entityName = #"Deleted";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(deletedpics like %#)", value];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
BOOL returnBool = [objects count] >= 1 ? YES : NO;
return returnBool;
}
One optimization is to replace
NSArray *objects = [context executeFetchRequest:request error:&error];
by
[request setFetchLimit:1];
NSUInteger count = [context countForFetchRequest:request error:&error];
if you only want to check for the existence of objects.
But I assume that the main performance problem is the wildcard search with LIKE, which is slower than searching with ==.
Instead of storing the status and the picture number in one
attribute as deleted.<status>.<picturenumber> (as you wrote in a comment) it would probably be better to use separate attributes status and picturenumber in the entity.
Then you can search for a picture number with the predicate
[NSPredicate predicateWithFormat:#"picturenumber == %d", i];
which should be much faster. If necessary, you can additionally index that property (as #flexaddicted mentioned in a comment).

Resources