I am still trying to wrap my head around Core Data and am having trouble letting go of SQL. I know that they are not the same but I am finding it hard to recreate what I can do in SQL. I am hoping someone can help.
What I want to do is create a list of Clients and return a sum of their current bill. I have two entities:
Client
name
address
etc
Billing
Client
date
amount
etc
I have created a one-to-many relationship between the 2 entities and have my NSEntityDescription pointing to Client.
Now what I want to happen in SQL terms is this:
SELECT *, SUM(amount) as Total FROM Clients INNER JOIN Billing......"
Can this be done with one Fetch Request or would I do 2 Requests and then merge them?
Can I somehow use valueForKey:#"billing.#sum.amount"?
Also could anyone recommend a great Core Data book (or website) for beginners, intermediate level?
Can this be done with one Fetch Request or would I do 2 Requests and then merge them?
Yes it can be done with one request. see here
/*UNTESTED*/
- (NSNumber*) billingSumForClient:(NSManagedObjectID*)clientId
context:(NSManagedObjectContext*)context
{
NSNumber* total = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Billing"];
[request setResultType:NSDictionaryResultType];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"amount"];
NSExpression *sumExpression = [NSExpression expressionForFunction:#"sum:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"total"];
[expressionDescription setExpression:sumExpression];
[expressionDescription setExpressionResultType:NSDateAttributeType];
[request setPredicate:[NSPredicate predicateWithFormat:#"client == %#",clientId]];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
NSError *error = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (&error) {
// Handle the error.
} else {
if ([objects count] > 0) {
total = [[objects objectAtIndex:0] valueForKey:#"total"];
}
}
return total;
}
Can I somehow use valueForKey:#"billing.#sum.amount"?
Yes. see here(#Daniel link)
This might be faster if all your billings are already faulted into your current context.
Related
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 ?
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);
}
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.
I'm creating a timeclock application where there is a ClockPunch NSManagedObject model with properties representing a clockin and clockout punch. Each ClockPunch instance has a relationship with an Employee in the Employees model (which also has a one-to-many relationship with each punch). I want to be able to write an NSPredicate that will give me the employee with their most recent clockin punch. I can then determine if they are clockedin or clocked out by whether or not they have a clockout punch to match. I do not want to pull all the punches in and then sort them, cause there could be 1000's for each employee. What should I do?
My Model looks like this
Employees<->>ClockPunches
I tried something very similar recently like this. I tried finding the registration with the maximum date.
NSFetchRequest* request = [Registration fetchRequest];
NSExpression *date = [NSExpression expressionForKeyPath:#"time"];
NSExpression *maxDate = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:date]];
NSExpressionDescription *d = [[[NSExpressionDescription alloc] init] autorelease];
[d setName:#"maxTime"];
[d setExpression:maxDate];
[d setExpressionResultType:NSDateAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:d]];
NSError *error = nil;
NSArray *objects = [Registration executeFetchRequest:request];
if (objects == nil) {
// Handle the error.
} else {
if (0 < [objects count]) {
NSLog(#"Maximum date: %#", [[objects objectAtIndex:0] valueForKey:#"type"]);
}
}
You can find more details on this page. https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html
However the part that I'm still missing is how to only look into the registrations of a specific employee... I tried combining the request setting an NSPredicate but somehow they can't be used together.
NOTE: I'm using restkit for my coredata access, follow the link to get a pure core data example (only small part of code is different)
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"];