CoreData Math Functions - iphone

I've got a column with integer values in CoreData. While retrieving results from it, I want the column values to be subtracted with a number.
Something like: columnValue - someNumber (this number is entered by user)
I know I may have to use NSPredicate for this, but am unaware if there's a function or syntax for it.
The alternate right now I have is to iterate all column values and subtract with 'someNumber'. But I think there should be a better and efficient way to do this.
Edit: Code from #salo.dm 's answer
- (NSDictionary *)myFetchResults {
//Predicate works fine
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:pred1, pred2, nil]];
/*Sort Descroptor - Sorting by 4 columns*/
NSSortDescriptor *sortDesc1 = [NSSortDescriptor sortDescriptorWithKey:#"Column1" ascending:YES];
NSSortDescriptor *sortDesc2 = [NSSortDescriptor sortDescriptorWithKey:#"Column2" ascending:YES];
NSSortDescriptor *sortDesc3 = [NSSortDescriptor sortDescriptorWithKey:#"Column3" ascending:YES];
NSSortDescriptor *sortDesc4 = [NSSortDescriptor sortDescriptorWithKey:#"Column4" ascending:YES];
/*Get Data*/
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableName" inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDesc1, sortDesc2, sortDesc3, sortDesc4, nil]];
NSArray *listData = [context executeFetchRequest:fetchRequest error:nil];
/*Create subtract expression*/
NSExpressionDescription *subExp1 = [[NSExpressionDescription alloc] init];
[subExpLatitude setName:#"subtraction1"];
[subExpLatitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column3"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber1]],
nil]]];
[subExp1 setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *subExp2 = [[NSExpressionDescription alloc] init];
[subExpLongitude setName:#"subtraction2"];
[subExpLongitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column4"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber2]],
nil]]];
[subExp2 setExpressionResultType:NSDoubleAttributeType];
/*Get difference data*/
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, subExp2, nil]];
NSArray *listDifference = [context executeFetchRequest:fetchRequest error:nil];
NSLog(#"Subtraction 1: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction1"]);
NSLog(#"Subtraction 2: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction2"]);
NSMutableDictionary *dictResult;
[dictResult setObject:listData forKey:#"Data"]
[dictResult setObject:listDifference forKey:#"Difference"]
return dictResult;
}
Edit: Get coredata object
This doesn't work.
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]]; //Searches for a column for the name specified
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Had to change it to below to get it working (Assuming this is the correct way)
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForEvaluatedObject]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];
I added expEntity to the setPropertiesToFetch list. Now I get two values in the dictionary.
{
TableNameEntity = "0x5e22120 <x-coredata://1A659A52-9321-4ACD-992B-04F20E7BDCED/TableNameEntity/p1640>";
subtractionValue = "-24.13";
}
When I try to retrieve and access TableNameEntity from the dictionary, the app crashes.
TableNameEntity *tableEntity = (TableNameEntity *)[dict objectForKey:#"TableNameEntity"];
tableEntity.column1 //This is not the exact code. But this operation crashes with error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSObjectID_48_0 column1]: unrecognized selector sent to instance 0x5e22120'
Here if you notice, the value for key TableNameEntity is contained in quotes, so I guess its being returned as a string.
See if you can correct what I've done wrong.
I've tried an alternate to get columns values in the dictionary. Here it is (this works fine). But I guess its not a good approach.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, #"column1", #"column2", ... #"columnN" nil]];

You could make the calculation in a fetch request as follows:
- (NSArray *)myFetchResults
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"myEntity" inManagedObjectContext:myContext];
request.resultType = NSDictionaryResultType;
NSExpressionDescription *subExDescr = [[NSExpressionDescription alloc] init];
[subExDescr setName:#"subtraction"];
[subExDescr setExpression:[NSExpression expressionForFunction:#"subtract:from:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForConstantValue:[NSNumber numberWithInt:someNumber]],
[NSExpression expressionForKeyPath:#"myAttribute"],
nil]]];
[subExDescr setExpressionResultType:NSInteger64AttributeType];
request.propertiesToFetch = [NSArray arrayWithObject:subExDescr, nil];
NSError *err = nil;
NSArray *results = [self.moContext executeFetchRequest:request error:&err];
[request release];
[err release];
return results;
}
The fetch results will be an array of dictionaries. You can access the result for the nth value in the column as follows:
NSArray *results = [self myFetchResults];
NSDictionary *nthDict = [results objectAtIndex:n];
NSInteger nthValue = [nthDict objectForKey:#"subtraction"];
Note that this code is untested. As is, I believe it will operate on all items in the column. If you want to operate only on selected items, you can add a predicate to select the items you want to operate on.
You could also look up the documentation for NSExpression and build all sorts of different operations. The class is a bit dense, but the Apple documentation has some snippets of code that help to understand how to use it. And I hope the above example illustrates how to incorporate it into a fetch request.
EDIT: CORRECTION
The entity, of course, has to be specified in the fetch request. I had initially left that out, but have now corrected the code.
EDIT: RESPONSE TO COMMENT
I'm not sure I understand what you're asking, but this may be it. You can create expression descriptions as follows:
NSExpressionDescription *expLatitude = [[NSExpressionDescription alloc] init];
[expLatitude setName:#"latitude"];
[expLatitude setExpression:[NSExpression expressionForKeyPath:#"Column3"]];
[expLatitude setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Then, you add them to the propertiesToFetch array, as two more objects. Each dictionary in the fetch results will now have the latitude, the subtraction resulting from that same latitude, and the corresponding objectID of the entity that contained that latitude. The dictionaries will be ordered in the results array according to your sort descriptors. (I haven't tried the objectID expression, but I think it should work fine.)
Basically, your results are ordered in the exact same order as a traditional fetch request with the same predicate and the same sort descriptors, that is, for a fetch request with the default result type NSManagedObjectResultType.
I hope this answers your question. If not, don't hesitate to ask again. But, I may take a while to answer because it's sleep time for me now.
EDIT: RESPONSE TO 'GET COREDATA OBJECT'
Good catch on finding the correct expression to get the object ID! (Seeing it, the expression I offered for this now looks obviously wrong.)
As to the exception you're getting, it makes sense. The value returned in the fetch results is not the managed object itself, it's only the managed object's ID. To access the managed object, I think the following should work:
NSManagedObjectID *myObjectID = [dict objectForKey:#"TableNameEntity"];
TableNameEntity *tableEntity = (TableNameEntity *)[context objectWithID:myObjectID];
tableEntity.column1
The context above is the NSManagedObjectContext.
However, I think I prefer your final solution. I didn't know you could combine NSExpressionDescriptions with properties in the propertiesToFetch array. Good to know!
More importantly, it may be faster to get all the properties you need in the fetch than to get only the objectID from the fetch and get the properties later. Getting the objectID generally does not fire the fault for the entity. I believe the fault will be fired later, when you access the properties. It will fire once, when accessing the first property, or multiple times, once for each property. (I'm not sure which.) [For an explanation of faulting, see Firing Faults.]
My recommendation is that including all the properties you need in propertiesToFetch is the best approach. (You may try getting the objectID, if you prefer. But, if you find it's slow, you can go back to getting all the properties in the fetch.)
Fetch requests and expressions are poorly documented. You have to play with them a bit to get the syntax right. You seem to be doing very well.

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;

Fetch request with multiple predicates?

I have an entity Tags, and I want to perform a fetch to get all Tags and assign that to a NSFetchedResultsController. But, I want the first object in the fetched results to be the Tag with tagName property equal to "All", then the rest sorted by alphabetical order. Currently I'm doing this, which just returns all of the tags in alphabetical order, but I want the tag named "All" to be first always, then the rest in alphabetical order.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Tag" inManagedObjectContext:appDelegate.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *lastDescriptor =
[[[NSSortDescriptor alloc] initWithKey:#"tagName"ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastDescriptor]];
You need to use a comparator created by you, not one default, so you can do something like that:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Tag" inManagedObjectContext:appDelegate.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *lastDescriptor =
[[[NSSortDescriptor alloc] initWithKey:#"tagName" ascending:YES comparator:^NSComparisonResult(NSString* tag1, NSString* tag2) {
if ([tag1 isEqualToString:#"All"]) return NSOrderedAscending;
if ([tag2 isEqualToString:#"All"]) return NSOrderedDescending;
return [tag1 compare:tag2];
}] autorelease];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastDescriptor]];
Edit
Like #Andrew Madsen told you can use a comparator, I do not see that he already have answered your question.
You can use a selector too, just need to implement it in your model a method to do the comparison, what I made a mistake and believed that was what you are doing, from apple doc:
selector
The method to use when comparing the properties of objects,
for example caseInsensitiveCompare: or localizedCompare:. The selector
must specify a method implemented by the value of the property
identified by keyPath. The selector used for the comparison is passed
a single parameter, the object to compare against self, and must
return the appropriate NSComparisonResult constant. The selector must
have the same method signature as:
- (NSComparisonResult)localizedCompare:(NSString *)aString
You can do this with NSSortDescriptor itself using a custom compare method. However, you can also sort the results of the fetch request after you get them back. NSArray has a method called -sortedArrayUsingComparator: that allows you to sort the array using a block, making custom sort behavior like this pretty easy. See below for an example:
NSArray *sortedResults = [results sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *tag1 = [obj1 tagName];
NSString *tag2 = [obj2 tagName];
BOOL tag1IsAll = [tag1 isEqualToString:#"All"];
BOOL tag2IsAll = [tag2 isEqualToString:#"All"];
if (tag1IsAll && !tag2IsAll) return NSOrderedAscending;
if (tag2IsAll && !tag1IsAll) return NSOrderedDescending;
return [tag1 compare:tag2];
}];

issue with sorting data in UITableview

I have implement a UItable with data sourced via Core Data. The table works fine and presents the data correctly, drills down.. etc.
However, it has the following problem: it presents the content data in a different order every time. I would like it at least to appear consistently or even better alphabetically.
Any ideas on why this might be happening or a specific property or method I should be reviewing in the docuemntation?
Help much appreciated
You need to set an NSSortDescriptor on your NSFetchRequest
NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptorName]];
Then create your NSFetchedResultsController with the fetchRequest object.
This would cause your list to appear sorted alphabetically using the property "name" and ignore case while sorting.
You can simply add a sort descriptor to the request. In this example, the data object has a numeric column for "sortOrder", but you could sort on most anything.
- (NSMutableArray *)loadData {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameOfEntity" inManagedObjectContext:context];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
[request release];
return [mutableFetchResults autorelease];
}
I have not done much with CoreData other than to read about it and review some examples, but I would say this:
It sounds like your data is simply coming out of CoreData unsorted, and the order is in fact not guaranteed UNLESS you provide some additional hint about how you would like it sorted, with say a sort descriptor or something.
Sorry I can't provide a concrete reference, but there has to be a way to both fetch data and specify the order in which it is returned.
Alternatively, you could (behind the scenes) fetch all the table data from CoreData and then sort it yourself, but I think that defeats the purpose of using CoreData in the first place and discards a lot of the functionality of CoreData that is likely more efficient than anything you could write yourself to massage the data.

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)
}

How to do an fetch request with expressions like this on the iPhone?

The documentation has an example on how to retrieve simple values only, rather than managed objects. This remembers a lot SQL using aliases and functions to only retrieve calculated values. So, actually pretty geeky stuff. To get the minimum date from a bunch of records, this is used on the mac:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[request setEntity:entity];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"creationDate"];
// Create an expression to represent the minimum value at the key path 'creationDate'
NSExpression *minExpression = [NSExpression expressionForFunction:#"min:" arguments:[NSArray arrayWithObject:keyPathExpression]];
// Create an expression description using the minExpression and returning a date.
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value.
[expressionDescription setName:#"minDate"];
[expressionDescription setExpression:minExpression];
[expressionDescription setExpressionResultType:NSDateAttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
NSLog(#"Minimum date: %#", [[objects objectAtIndex:0] valueForKey:#"minDate"];
}
}
[expressionDescription release];
[request release];
Nice, I though - but having a deep look into NSExpression -expressionForFunction:arguments: it turns out that iPhone OS does NOT support the min: function.
Well, probably there's a nifty way to use an own function for this kind of stuff on the iPhone as well? Because on thing I'm already worrying about is, how I'm gonna sort a table based on the calculated distance of targets on a map (location-based stuff).
You can implement your own function, then use
+ (NSExpression *)expressionForFunction:(NSExpression *)target selectorName:(NSString *)name arguments:(NSArray *)parameters
this returns an expression which will return the result of invoking on a given target a selector with a given name using given arguments.
thnx #dontwatchmyprofile
I just tested
NSExpression *maxExpression = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:keyPathExpression]];
seems to be working fine with iOS 3.2 SDK.