Sahil Khanna
Sahil Khanna

Reputation: 4382

CoreData Math Functions

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]];

Upvotes: 2

Views: 1667

Answers (1)

salo.dm
salo.dm

Reputation: 2315

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.

Upvotes: 4

Related Questions