Group By for Core Data Results - ios

I am using Core Data and generally, I have a Game, a Game Phase, and points scored for different types of actions (lets say pointsA, pointsB).
Each game consists of two phases and there are hence for each player a total points per phase and then per game (phase 1 + phase 2).
My Score Entity in Core Data has:
Player (Relationship to player),
Game (Relationship to game),
Phase (attribute),
PointsA (attribute),
PointsB (attribute).
So each player has a record for a Score in a Phase in a Game.
In order to get in fetch all points for a given player AGGREGATED BY GAME (so SQL equivalent of "Group By"). I managed to use this code and IT WORKS:
CODE:
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Score" inManagedObjectContext:self.managedObjectContext];
fetchRequest.predicate=[NSPredicate predicateWithFormat:#"player == %#",_currentPlayer];
NSExpressionDescription* ex = [[NSExpressionDescription alloc] init];
[ex setExpression:[NSExpression expressionWithFormat:#"#sum.pointsA"]];
[ex setExpressionResultType:NSDecimalAttributeType];
[ex setName:#"pointsA"];
NSExpressionDescription* ex2 = [[NSExpressionDescription alloc] init];
[ex2 setExpression:[NSExpression expressionWithFormat:#"#sum.pointsB"]];
[ex2 setExpressionResultType:NSDecimalAttributeType];
[ex2 setName:#"pointsB"];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:#"Game",ex, ex2,nil]];
[fetchRequest setPropertiesToGroupBy:[NSArray arrayWithObjects:#"Game",nil]];
[fetchRequest setResultType:NSDictionaryResultType ];
results= [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
My question is: Suppose I have MANY MORE POINTS TYPES, as in pointsC, PointsD, etc.. (let's say scores for many more different kinds of actions). Do I have to use a SEPARATE NSExpressionDescription (ex and ex2 above) for all of these ?
Is this really how long winded it is in Core Data? Is there a quicker way?
I am relatively new to Core Data.
For those who are wondering how to parse through the results set:
for (id Res in results) {
NSLog(#"pointsA: %# ", Res[#"pointsA"] );
NSLog(#"pointsB: %# ", [Res valueForKey:#"pointsB"] );
//both work
}

OK, answering my own question: I guess a more elegant way would be to feed in an array of items with the Core Data attribute names, which can then be as long as you want:
(NSArray*)items contains pointsA, pointsB, pointsC, pointsD, all the way to z and beyond if you so require.
///Code///
-(void)fetchRes:(NSArray*)items
{
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Score" inManagedObjectContext:self.managedObjectContext];
fetchRequest.predicate=[NSPredicate predicateWithFormat:#"player == %#",_currentPlayer];
NSMutableArray *propsArray=[[NSMutableArray alloc]initWithObjects:#"Game", nil];
for (int i=0;i<items.count;i++)
{
NSString *desc=[[NSString alloc]initWithFormat:#"#sum.%#",items[i]];
NSExpressionDescription* ex3 = [[NSExpressionDescription alloc] init];
[ex3 setExpression:[NSExpression expressionWithFormat:desc]];
[ex3 setExpressionResultType:NSDecimalAttributeType];
[ex3 setName:items[i]];
[propsArray insertObject:ex3 atIndex:i+1];
}
[fetchRequest setPropertiesToFetch:propsArray];
[fetchRequest setPropertiesToGroupBy:[NSArray arrayWithObjects:#"Game",nil]];
[fetchRequest setResultType:NSDictionaryResultType ];
results=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] ;
}
Not sure what the performance / memory consequences are ?

Related

CoreData Sum Performance

I have some theoretical question to ask about Core Data and sum function.
I try to sum values from Core Data table with three ways.
fetch all and use expression to sum it up :
NSArray * array1 = [self getAll:self.managedObjectContext];
int sum = [[array1 valueForKeyPath:#"#sum.sum"] intValue];
fetch all and use for loop:
int sum2 = 0;
NSArray * array2 = [self getAll:self.managedObjectContext];
for (Test * t in array2) {
sum2 = sum2 + [t.sum intValue];
}
let Core Data sum it.
NSArray * array = [self getAllGroupe:self.managedObjectContext];
NSDictionary * i = [array objectAtIndex:0];
id j = [i objectForKey:#"sum"];
(NSArray *)getAllGroupe:(NSManagedObjectContext*)Context{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Test"
inManagedObjectContext:Context];
NSExpressionDescription* ex = [[NSExpressionDescription alloc] init];
[ex setExpression:[NSExpression expressionWithFormat:#"#sum.sum"]];
[ex setExpressionResultType:NSDecimalAttributeType];
[ex setName:#"sum"];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:ex, nil]];
[fetchRequest setResultType:NSDictionaryResultType ];
NSError *error;
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [Context executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
surprisingly the
way was the slowest (for 1.000.000 data --> 19.2 s), the
way was faster (for 1.000.000 data --> 3.54 s) and the
way was the fastest (for 1.000.000 data --> 0.3 s)
Why is this?
If I understand right even core data need to go through all 1.000.000 datas and sum it. Is this because use more cores if there are available?
No CoreData doesn't do the summing on it's own - it delegates that to it's backing sqllite database which is optimized for things like that.
Basically CoreData sends a select SUM(sum) from table; to it's db and it's performed there.

Core Data - detect equal properties in fetched objects

My core data model:
Contact
=======
name
phone number (unique)
I need to display a table view of contacts with the following condition:
if there is more than one contact with the same name - show name+number
otherwise - show only name
Fo for example, if my core data contacts are:
Michael, 11112221
Jon, 33438282
Jon, 72727272
Lisa, 99939393
My table view should present:
Jon (33438282)
Jon (72727272)
Lisa
Michael
Currently I'm using the following NSFetchRequest to show the contact list:
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Contact"];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)]];
request.fetchBatchSize = 20;
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:#"nameFirstLetter"
cacheName:nil];
I'm looking for the most efficient way to know which name appears more than once so I can show the number next to the name.
If you're using NSSQLiteStoreType as your persistent store you can fetch duplicate items with an NSExpression:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:self.managedObjectContext];
NSAttributeDescription *nameDesc = [entity.attributesByName objectForKey:#"name"];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"name"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:" arguments:#[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"count"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
NSError *error = nil;
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Contact"];
[fetch setPropertiesToFetch:#[nameDesc, expressionDescription]];
[fetch setPropertiesToGroupBy:#[nameDesc]];
[fetch setResultType:NSDictionaryResultType];
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
NSArray *duplicates = [results filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"count > 1"]];
NSArray *duplicateNames = [duplicates valueForKeyPath:#"name"];
duplicateNames contains the name of contacts which name appears multiple times in the db. So, whenever a contact displayed in the table view, just query the duplicateNames array to check whether the actual contact is a duplicate or not.
The easiest way if you only want to modify the display I would modify tableView:cellForRowAt... to check the name of the person above and below the current one
in basic mock code:
- tableView:cellForRowAtIndexPath:(id)path {
....
Person *prior = ...
Person *next = ...
if(prior.name == current.name || next.name == current.name) {
//show name+number
}
}

Retrieving count of a core data relation

I searched high and low but I couldn't find exactly what I was looking for. My question is similar to this, but slightly different:
Core Data - Count of Related Records
Let's say I have a Car entity which has a one to many relation with a Person entity. This means that the car could have multiple people driving it, but each person drives only one car.
I want to be able to execute only one predicate wherein I could achieve the following:
All cars which are 'red'.
Return only the 'Year' and 'Color' attributes of the matching car.
Return a count of how many people are driving this car (i.e the size of the NSSet of People inside each resulting Car).
Is it possible to do all this with one query?
I know how to do this with multiple queries. I would just use setPropertiesToFetch and use a filtered predicate to achieve 1 and 2 above. I would then perform another count query (countForFetchRequest) on the Persons entity for every car to find how many Person(s) drive each car.
The key is the 3rd requirement above. I want to do everything in one predicate and I don't want to bring all of the Person entity objects into memory (performance) on the initial query. Furthermore it hurts to call another countForFetchRequest query for each car.
What's the best way to do this?
Thanks!
I cannot test this at the moment, but that should be possible by adding the following expression description to the "properties to fetch":
NSExpression *countExpression = [NSExpression expressionForFunction: #"count:" arguments: [NSArray arrayWithObject:[NSExpression expressionForKeyPath: #"drivers"]]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"driversCount"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
Return only 'red' cars:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"color LIKE 'red'"];
Return a count of how many people are driving this car:
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"people"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:"
arguments:#[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"count"];
[expressionDescription setExpression:countExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
Return only the 'year' and 'color' attributes (and the count):
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car"
inManagedObjectContext:context];
NSDictionary *attributes = [entity attributesByName];
NSArray *properties = #[expressionDescription, attributes[#"year"], attributes[#"color"]];
Build and execute the fetch request:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:properties]; // return only count, year & color
[request setPredicate:predicate]; // return only red cars
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
Process the results:
if (results) {
for (NSDictionary *result in results) {
NSLog(#"Year: %#", result[#"year"]);
NSLog(#"Color: %#", result[#"color"]);
NSLog(#"Drivers: %#", result[#"count"]);
}
}
else {
NSLog(#"Error: %#", error);
}

Getting a maximum value from a core data relationship

I search the net for answers to this problem, but unfortunately I don't think I am fluent enough in core data procedures to actually include the right combo of keywords.
I have two entities... Users and Bookmarks with a one-to-many relationship.
Users : string:firstName, string:lastname, string:iconImage
with a relationship to Bookmarks - inverse
Bookmarks : string:title, string:url, string:content, image:Binary Data, order:Integer32
with a relationship to Users - inverse
My goal here is to query the bookmarks for a particular user and find the highest number for the key:order, so that when I add an new bookmark, it will be one larger than the max. I have seen the Apple example, which works and makes sense, but I need a little more. The example returns the maximum value for all records in that entity.
-(NSNumber*) getNextBookmarksOrderForUser:(NSManagedObjectID*)userID
{
NSNumber *highOrder;
Users *user =[self getUserByID:userID];
if (user)
{
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"order"];
NSExpression *highestOrderingNumber = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc]init];
[expressionDescription setName:#"maxOrdering"];
[expressionDescription setExpression:highestOrderingNumber];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Bookmarks" inManagedObjectContext:[_dataContext managedObjectContext]];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
[request setResultType:NSDictionaryResultType];
NSError *error = nil;
NSArray *objects = [[_dataContext managedObjectContext] executeFetchRequest:request error:&error];
if(objects == nil) {
// Handle the error
}
else {
if ([objects count] > 0) {
highOrder = [NSNumber numberWithInt:[[[objects objectAtIndex:0] valueForKey:#"maxOrdering"]intValue]];
NSLog(#"Highest ordering number: %#", highOrder);
}
}
return highOrder;
}
return nil;
}
So if I have two users, one with 10 bookmark and the other with 25, the above code will always return 25. I tried adding a predicate:
NSPredicate *byUser = [NSPredicate predicateWithFormat:#"self == %#", user];
[request setPredicate:byUser];
Does anyone have any suggestions? I Know that I could return all bookmark for a user and the write the code to find my value, but I would like to do it the most efficient way.
Thank you for any help.
From my comment
I think you should use a predicate like this [NSPredicate predicateWithFormat:#"users == %#", user]; Since you are querying against Bookmarks.

Core Data equivalent for sqlite query

I use Core Data for an iPhone app.
There is "Flight" entity with a "start" and "duration" property.
The flights are listed on a paginated view, where I need to sum the duration per page and the duration rollup sum.
In native sqlite following solution works:
select sum(pg.zduration) from (select zduration,zstart from zflight order by zstart limit %i,%i) as pg",offset,limit
So on first page, with a page size of 5, I get duration sum and same rollup duration with offset=0 and limit=5.
On second page, I get the duration sum with offset=5 and limit=5. The rollup sum with offset=0 and limit=10.
And so on..
Now the Question:
How would I solve that with Core Data, NSExpression, NSExpressionDescription and NSFetchRequest instead of sqlite? Of course, I would not like to load all flight objects in memory...
So I am able to caculate the duration for all flights:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Flight" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
NSSortDescriptor *startSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"start"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:startSortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
request.fetchOffset=onPage*pageSize;//does not help, cause offset and limit are applied to the result
request.fetchLimit=pageSize;//does not help, cause offset and limit are applied to the result
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"duration"];
NSExpression *sumExpression = [NSExpression expressionForFunction:#"sum:" arguments:[NSArray arrayWithObject:keyPathExpression]];
// Create an expression description using the minExpression and returning a date.
NSExpressionDescription *expressionDescription1 = [[NSExpressionDescription alloc] init];
[expressionDescription1 setName:#"durationSum"];
[expressionDescription1 setExpression:sumExpression];
[expressionDescription1 setExpressionResultType:NSInteger64AttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObjects:expressionDescription1,nil]];
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:request error:&error];
if(error!=nil){
[NSException raise:#"Sum Page Duration failed" format:#"%# Error:%#", [[error userInfo] valueForKey:#"reason"],error];
}
if (objects!=nil && [objects count] > 0) {
return (NSNumber*)[[objects objectAtIndex:0] valueForKey:#"durationSum"];
}
return 0;
As you said, the limit and offset set on the fetch request are applied to the result and NSExpression won't work well in this case. You could operate on the returned objects, after they've been offset and limited by the fetch request, using a collection operator rather than NSExpression, e.g.:
NSNumber *durationSum = [objects valueForKeyPath:#"#sum.duration"];

Resources