How to sum on a calculated property? - iphone

I'm sure I'm guilty of trying to apply SQL logic to Core Data, but even after reading the Apple docs I'm still not sure of the correct approach.
What would be the best strategy for summing the difference of two date properties in a Core Data entity? I have a "numHours" calculated property on the "appt" class but not in the xcdatamodel. The code below fails with a "keypath not found in entity" error. Many thanks for any advice.
from appt class
- (float)numHours {
float hours = ([self.endTime timeIntervalSinceDate:self.startTime] - [self.durationOfBreak intValue]) / 3600.00;
return hours;
}
attempt at fetching sum of "numHours" property
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"appt" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSPredicate *datePredicate = [NSPredicate predicateWithFormat:#"date >= %# && date < %#", self.startDate, self.endDate];
[request setPredicate:datePredicate];
[request setResultType:NSDictionaryResultType];
NSExpression *numHoursPath = [NSExpression expressionForKeyPath:#"numHours"];
NSExpression *hoursSum = [NSExpression expressionForFunction:#"sum:"
arguments:[NSArray arrayWithObject:numHoursPath]];
NSExpressionDescription *debitExpressionDescription = [[NSExpressionDescription alloc] init];
[debitExpressionDescription setName:#"totalhours"];
[debitExpressionDescription setExpression:hoursSum];
[debitExpressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:debitExpressionDescription]];
[debitExpressionDescription release];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];

Jeffery,
You cannot fetch based upon an attribute that isn't actually in the model. The error message, "keypath not found in entity," plainly says this. You can confirm this for yourself by browsing the SQLite DB and you'll see there is no attribute for the SQL to query. Hence, your fetch request must fail. NSExpression *numHoursPath is a valid expression. It can be applied to collections after you've fetched them. You just cannot use them in a fetch request.
Andrew

Related

How to make NSExpression's expressionForFunction:withArguments: honour the fetch request's predicate

I'm using Core Data to store simple entities that consist of a value and a timestamp. I'm looking to make a fetch request which returns the latest value added, as well as a running average of all values. This all seemed straightforward enough until I attempted to use an NSPredicate to filter the results to within a certain time period.
I'm using NSExpression's expressionForFunction:withArguments: method to determine the average. By setting the launch flag -com.apple.CoreData.SQLDebug 1, I can clearly see that only the latest value is adhering to my date predicate. The average calculating is instead being performed as a subquery, but not taking my date predicate into account:
SELECT (SELECT avg(t1.ZVALUE) FROM ZEVENT t1 WHERE t1.Z_ENT = ?), t0.ZVALUE FROM ZEVENT t0 WHERE ( t0.ZTIMESTAMP >= ? AND t0.Z_ENT = ?) ORDER BY t0.ZTIMESTAMP DESC
What's the most efficient (and scalable) way of determining the average value while still honouring my NSFetchRequest's predicate?
For reference, here is my code in it's entirety:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Reading" inManagedObjectContext:moc];
[request setEntity:entity];
[request setSortDescriptors:#[sortDescriptor]];
[request setResultType:NSDictionaryResultType];
// Filter by date where necessary
NSPredicate *datePredicate = [NSPredicate predicateWithFormat:#"(timestamp >= %#)", toDate];
[request setPredicate:datePredicate];
NSExpression *valueExpression = [NSExpression expressionForKeyPath:valueKey];
NSExpressionDescription *valueDescription = [[NSExpressionDescription alloc] init];
[valueDescription setName:#"value"];
[valueDescription setExpression:valueExpression];
[valueDescription setExpressionResultType:NSDoubleAttributeType];
NSExpression *avgExpression = [NSExpression expressionForFunction:#"average:" arguments:[NSArray arrayWithObject:valueExpression]];
NSExpressionDescription *avgDescription = [[NSExpressionDescription alloc] init];
[avgDescription setName:#"averageReading"];
[avgDescription setExpression:avgExpression];
[avgDescription setExpressionResultType:NSDoubleAttributeType];
[request setPropertiesToFetch:#[avgDescription, valueDescription]];
I see two errors. There's no initialization shown for toDate. I also notice that you are passing setPropertiesToFetch: an array of NSExpressions, but the documentation calls for an array of NSPropertyDescriptions. I would expect that discrepancy to cause a null result, and populated NSError, when you execute the fetch request.
What result do you see from executeFetchRequest:error:? Be sure to check the NSError result. Idiom is something like this:
NSError *error;
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (!results) {
NSLog(#"%# fetch error for request %#: %# %#", fetchRequest,
error.localizedDescription, error.localizedFailureReason);
}
I would take a different approach. One fetch request has a limit of 1, sort timestamp descending, and returns the latest timestamp. Add a predicate if you like, for time restriction. Then use a second fetch request to compute the average of the timestamps. You could even encapsulate these calls into their own methods:
-(NSDate *)latestTimestamp:(NSManagedObjectContext *)moc;
-(NSNumber *)averageValueSinceTime:(NSDate *)intervalStart
context:(NSManagedObjectContext *)moc;

how to fetch minimum value of count of to-many sub models in core data

I have two models Practice and PracticeRecord with a to-many relationship between them. Now I want to retrieve an array of Practice objects whose number of associated practiceRecord objects are minimum but greater than zero. I wrote below code but it didn't work and exception happened when fetching the request. Can anybody give an elegant solution please. Thanks in advance.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Practice"];
request.predicate = [NSPredicate predicateWithFormat:#"practiceRecords.#count > 0"];
[request setResultType:NSDictionaryResultType];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"practiceRecords.#count"];
NSExpression *minExpression = [NSExpression expressionForFunction:#"min:" arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"minPracticeRecordTimes"];
[expressionDescription setExpression:minExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
NSError *error = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
NSLog(#"Minimum practice record times: %#", [[objects objectAtIndex:0]
valueForKey:#"minPracticeRecordTimes"]);
}
}
I'd guess the issue is using an aggregate function (practiceRecords.#count) in the key path
If you consider the SQL this will generate, it'll be something like this:
SELECT min(count(practiceRecords)) AS minPracticeRecordTimes
FROM Practice
WHERE count(practiceRecords) > 0
which is of course invalid.
If you need to do this, maybe de-normalize your entities a bit, and add a count field to Practice, which is updated each time a PracticeRecord is added or removed, then you can use a straight fetch request.

How do I get the last record of a Core Data database?

I have a core data entity called images that has just 2 fields:
imageName = NSString
timeStamp = NSNumber
I am trying to simulate a kind of stack LIFO (last in first out).
Inserting a new entry is easy but what about reading the last entry added to the entity?
All images are added with a timestamp, obtained by using
time_t unixTime = (time_t) [[NSDate date] timeIntervalSince1970];
an integer that is equal to the number of seconds since 1970
so, how do I retrieve the last inserted record of a core data (= the record that has the biggest timestamp number)???
thanks
Perform a fetch request, sorting the results by timeStamp.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:...];
// Results should be in descending order of timeStamp.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"timeStamp" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSArray *results = [managedObjectContext executeFetchRequest:request error:NULL];
Entity *latestEntity = [results objectAtIndex:0];
You might also want to restrict the number of results using NSFetchRequest's setFetchLimit:.
I have tried using the method that Chris Doble mentioned and found it to be very slow, especially if there are lot of records that would need to be pulled and checked against the timeStamp. If you want to speed things up, I am now setting an attribute called isMostRecent on my ManagedObject's that I may ever want to get the most recent from. When a new record is to be stored I just grab the most recent record that has this attribute set to YES and change it to NO then set the new record that is being stored to YES. The next time I need to grab to most recent record all I have to do is this...
+ (Photo*)latestPhotoForMOC:(NSManagedObjectContext*)context {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCoreDataEntityNamePhoto
inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isMostRecent == %#", [NSNumber numberWithBool:YES]];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"isMostRecent" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
Photo* photo = nil;
if (mutableFetchResults && mutableFetchResults.count > 0) {
photo = [mutableFetchResults objectAtIndex:0];
}
return photo;
}
I have found this to be much faster. Yes, it requires a little more on your part to ensure it is used properly and that you don't ever end up with more than one record marked as isMostRecent but for me this was the best option.
Hope this helps someone else too.
In Swift 4, declare:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = [Entity]()
func getLastRecord() {
let entityCount = (entity.count - 1)
let lastRecord = entity[entityCount] // This is the las attribute of your core data entity
print(lastRecord)
}

NSPredicate compare dates in CoreData

I have a managed object with two dates: dateOne and dateTwo and I want to retrieve the dates where dateTwo is older than dateOne.
Right now I am using the following predicate to fetch the objects:
NSArray *objects = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"dateOne > dateTwo", #""];
objects = [context executeFetchRequest:request error:nil];
[request release];
Sometimes it works, but other times the objects fetched have a dateTwo that is more recent than dateOne. Is there a better way to do something like this?
Thanks!
Perhaps you could add a BOOL property "dateTwoIsOlder" to the entity, then just fetch objects that adhere to that?
Why not just create a fetch request from core data and not do it programmatically?

How to determine number of objects in one-to-many relationship in CoreData

So, I've got a one-to-many relationship of Companies to Employees in CoreData (using a SQLite backend on iOS, if that's relevant). I want to create a predicate that only returns Companies that have 0 Employees associated with them. I could do it by getting all the Companies and iterating over them, but that would be (I assume) much slower.
Any ideas?
Thanks,
-Aaron
After trying #falconcreek's answer and getting an error (described in my comment on his answer), I did some googling and determined that the answer was
NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees.#count == 0"];
Now everything works über efficiently. Thanks!
Assuming your Company -> Employee relationship is named "employees"
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// the following doesn't work
// NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees = nil OR employees[SIZE] = 0"];
// use #count instead
NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees = nil OR employees.#count == 0"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (error)
{
// Deal with error...
}