Fetch aggregate data from NSManagedObject using another expression as argument to sum: expression - objective-c

Is it possible to use expression as argument for sum: expression?
I have entity (NSManagedObject class) Drink with properties costPerDrink, numberOfDrinks and drinkingDate.
I would like to get sum of total costs (numberOfDrinks multiplied by costPerDrink) within time period. While I have no problems getting sum on single property (e.g. sum of numberOfDrinks within time period), when I try to use sum expression on another (multiply) expression I got error:
NSInvalidArgumentException, reason: Unsupported argument to sum :
(
"costPerDrink * numberOfDrinks"
)
See code:
// Init fech
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Drink" inManagedObjectContext:[Service appContext]];
[request setEntity:entity];
// Return dictionary
[request setResultType:NSDictionaryResultType];
// Set conditions
[request setPredicate:[NSPredicate predicateWithFormat:#"(drinkingDate>=%#) AND (drinkingDate<=%#)", self.startDate, self.endDate]];
// Set 1st column to be returned - count of drinks
NSExpression *drinksExpression = [NSExpression expressionForKeyPath:#"numberOfDrinks"];
NSExpression *sumDrinksExpression = [NSExpression expressionForFunction:#"sum:" arguments:[NSArray arrayWithObject:drinksExpression]];
NSExpressionDescription *sumDrinksResultDescription = [[NSExpressionDescription alloc] init];
[sumDrinksResultDescription setName:#"sumDrinks"];
[sumDrinksResultDescription setExpression:sumDrinksExpression];
[sumDrinksResultDescription setExpressionResultType:NSInteger32AttributeType];
// Set 2nd column to be returned - total cost
NSExpression *costExpression = [NSExpression expressionForKeyPath:#"costPerDrink"];
NSExpression *totalExpression = [NSExpression expressionForFunction:#"multiply:by:"
arguments:[NSArray arrayWithObjects:costExpression,
drinksExpression, nil]];
NSExpression *sumCostExpression = [NSExpression expressionForFunction:#"sum:" arguments:[NSArray arrayWithObject:totalExpression]];
NSExpressionDescription *sumCostResultDescription = [[NSExpressionDescription alloc] init];
[sumCostResultDescription setName:#"sumCost"];
[sumCostResultDescription setExpression:sumCostExpression];
[sumCostResultDescription setExpressionResultType:NSDecimalAttributeType];
// Add expressions to fetch
[request setPropertiesToFetch:[NSArray arrayWithObjects: sumDrinksResultDescription, sumCostResultDescription, nil]];
// Execute fech
NSError *error;
NSArray *result = [[Service appContext] executeFetchRequest:request error:&error];
I perfectly understand that particular problem can be solved by adding auto-calculated totalCost property to Drink entity and then using sum: in fetch without multiply:by: expression but question remains - is it possible to use expression as argument for sum: expression?

Related

fetch all other values in row for maximum value of group by using core data in objective-c

I worked based on this link
Core data predicate with group and MAX()
Only two column values are fetched.
MaxDate and clientName.
I need all other values like client_id, number which is present in same row of MaxDate.
I have used same code. please suggest me to get other attributes of same row.
Equivalent for the sqlite query
"SELECT *, MAX(DATE)” \
"FROM IM_INFO GROUP BY CLIENTNAME;”
I have used this code.
NSExpression *dateKeyExpression = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDateExpression = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:dateKeyExpression]];
NSExpressionDescription *maxDateED = [[NSExpressionDescription alloc] init];
[maxDateED setExpression:maxDateExpression];
[maxDateED setName:#"maxDate"];
[maxDateED setExpressionResultType:NSDateAttributeType];
NSAttributeDescription *clientName = [[self entityDescription].attributesByName objectForKey:#"clientName"];
[request setPropertiesToFetch:[NSArray arrayWithObjects:clientName, maxDateED, nil]];
[request setPropertiesToGroupBy:[NSArray arrayWithObject:clientName]];
[request setResultType:NSDictionaryResultType];

nsexpression with custom function core data

i'm trying to group and count on date on a core data entity. since date cant be group due to different time. so i'm trying to pass custom function to nsexpression. but i get some errors i dont know what's the correct way to do it.
code for grouping and counting:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TEvents"
inManagedObjectContext:context];
NSDictionary *props = [entity propertiesByName];
NSPropertyDescription *propDesc3 = [props objectForKey:#"startDateTime"];
NSExpression *propExpr3 = [NSExpression expressionForKeyPath:#"startDateTime"];
NSExpression *countExpr3 = [NSExpression expressionForFunction:propExpr3 selectorName:#"dateShort" arguments:nil];
NSExpressionDescription *exprDesc3 = [[NSExpressionDescription alloc] init];
[exprDesc3 setExpression:countExpr3];
[exprDesc3 setExpressionResultType:NSDateAttributeType];
[exprDesc3 setName:#"dateStart"];
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"TEvents"];
[fr setPropertiesToGroupBy:[NSArray arrayWithObjects:propDesc3, nil]];
[fr setPropertiesToFetch:[NSArray arrayWithObjects:propDesc3, exprDesc3, nil]];
[fr setResultType:NSDictionaryResultType];
NSError * error = nil;
NSArray *results = [context executeFetchRequest:fr error:&error];
dateShort is a nsdate category function which converts datatime to user's current zone:
-(NSString *) dateShort
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:[NSCalendar currentCalendar]];
[formatter setTimeZone:[NSTimeZone localTimeZone]];
[formatter setDoesRelativeDateFormatting:YES];
[formatter setDateStyle:NSDateFormatterShortStyle];
[formatter setTimeStyle:NSDateFormatterNoStyle];
NSString *tmpValue = [formatter stringFromDate:self];
return tmpValue;
}
error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported function type passed to SQL store'
how to achieve this goal: convert datetime to current zone and group and count on (yy-mm-dd) on a core data entity?
I think you are on the wrong path here. There is a much simpler solution to your problem.
You should use a NSFetchedResultsController and use a transient property on your entity to serve as the section header. You calculate the date in a category of your managed object subclass. Then it is as simple as specifying your transient property name in the sectionNameKeyPath when creating the fetched results controller.
Apple has a good working example that for most purposes you can use out of the box.
I had to use NSExpression to achieve the goal. NSfetchResultController cannot group.

Objective-c. Fetching distinct values of attribute from core data

i have run into a problem that i can not solve. I have a "database" - read core data - where i have attribute that holds a value and level.
Something like that
value -------- level
55 -------------4
33 -------------4
50 -------------5
70 -------------6
44 -------------5
what i want now is a to extract all values from level 5 only and add them together. How can i achieve this ?I did found "fetch distinct values" on apple dev site, but this would apply to extracting all values from one attribute.
Help appreciated, thank you. If i have missed a similar topic then please provide me with a link. Thanks
You can use the following fetch request:
// Fetch request for your entity:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
[request setResultType:NSDictionaryResultType];
// Restrict result to "level == 5":
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"level == %d", 5];
[request setPredicate:predicate];
// Expression description for "#sum.value":
NSExpression *sumExpression = [NSExpression expressionForKeyPath:#"#sum.value"];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"sumValue"];
[expressionDescription setExpression:sumExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
[request setPropertiesToFetch:#[expressionDescription]];
NSArray *result = [context executeFetchRequest:request error:&error];
The result for your data is
(
{
sumValue = 94;
}
)
i.e. an array containing one dictionary with the sum of values with level=5.

Number of elements in NSArray where (....)

I am used to working in C# using the linq extensions (list.select(...).where(...) ext), and I was wondering if there was some way of doing the same sort of thing in Objective-C. This would save me from building a number of rather complicated queries using Core Data, which is great for some things, but perhaps not the best for complex queries (or maybe I'm just uninformed).
Is there some kind of equivalent for linq in Objective-C/Core Data?
EDIT: More specifically, I would like to count the number of elements that fit some criteria. Say my model has a field called date. I am trying to select the distinct dates, and then calculate how many of each date there are. In SQL this would be like a group by, and a COUNT aggregate.
Your question goes from very general ("linq equivalent?") to very specific (computing count by date). I'll just answer your specific question.
Unfortunately, NSArray doesn't have a built-in map or select method, but it does offer NSCountedSet, which will compute what you want:
NSCountedSet *dateSet = [NSCountedSet set];
for (id thing in array) {
[dateSet addObject:[thing date]];
}
for (NSDate *date in dateSet) {
NSLog(#"There are %d instances of date %#", [dateSet countForObject:date], date);
}
Change predicate , and "Date" keys with your props
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"Date"]];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)",sda ];
[fetchRequest setPredicate:predicate];
int c = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
Here was something posted with comes close
filtering NSArray into a new NSArray in objective-c
Anyway AFAIK you don't have some sort of linq in Objective-C but you have. Arrays and Blocks. And Blocks are functions. So you can really filter on anything in there.
Of course, Cora Data has many functions to make complex queries:
In example to get sum of elements, you have two major ways:
first - get your data to NSSet or NSArray and use #sum operator:
//assume that `childs` are NSArray of your child entities and ammount is attribute to sum, and has attributes start (date) and completion (integer)
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)", dzis];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error = nil;
if ([afetchedResultsController performFetch:&error]) {
NSArray *childs = afetchedResultsController.fetchedObjects;
NSNumber *sum=[childs valueForKeyPath:"#sum.ammount"];
}
second is using specific fetch for specific value with added NSExpressionDescription with a sum. This way is harder but better for larger db's
suppose if you have an array of your model objects, you could that with the following statement,
NSArray *distintDatesArray = [array valueForKeyPath:#"#distinctUnionOfObjects.date"];
for ( NSDate *date in distintDatesArray)
{
NSLog (#"Date :%# ,count : %d",date,[[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"date = %#",date]] count]);
}
This will have same effect as the group by query.

'Unsupported function expression CAST...' when using NSPredicate with SQLite Core Data store type

I'm attempting a fetch request with the following code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"dateModified > CAST(CAST(now(), \"NSNumber\") - %d, \"NSDate\")", (30*86400)];
NSFetchRequest *itemRequest = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
[itemRequest setPredicate:predicate];
NSArray *items = [[self managedObjectContext] executeFetchRequest:itemRequest error:nil];
...in order to fetch items where the 'dateModified > |now() - 30 days|'. The problem is that the above predicate works correctly (fetches properly) until I save my NSPersistentDocument as SQLite and re-open the document. When I re-open the document I get the following error:
Unsupported function expression CAST(CAST(now(), "NSNumber") - 2592000, "NSDate")
Why does this predicate work correctly up until save or if I save the document as XML, but does not work correctly if I save as SQLite? Is there a way to use this predicate with the SQlite store type?
P.S. I have also tried forming the predicate with the following code but get the same error:
//"now()" is a function and takes no arguments
NSExpression * now = [NSExpression expressionForFunction:#"now" arguments:[NSArray array]];
//CAST(now(), 'NSNumber')
NSArray * castNowArguments = [NSArray arrayWithObjects:now, [NSExpression expressionForConstantValue:#"NSNumber"], nil];
NSExpression * castNow = [NSExpression expressionForFunction:#"castObject:toType:" arguments:castNowArguments];
//CAST(now(), 'NSNumber') [+/-] {a time interval}
NSArray * relativeTimestampArguments = [NSArray arrayWithObjects:castNow, [NSExpression expressionForConstantValue:[NSNumber numberWithDouble:(30*86400)]], nil];
NSExpression * relativeTimestamp = [NSExpression expressionForFunction:#"from:subtract:" arguments:relativeTimestampArguments];
//CAST(CAST(now(), 'NSNumber') [+/-] {a time interval}, 'NSDate')
NSArray * castToDateArguments = [NSArray arrayWithObjects:relativeTimestamp, [NSExpression expressionForConstantValue:#"NSDate"], nil];
NSExpression * castToDate = [NSExpression expressionForFunction:#"castObject:toType:" arguments:castToDateArguments];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:#"dateModified"]
rightExpression:castToDate
modifier:NSDirectPredicateModifier
type:NSGreaterThanOrEqualToComparison
options:0];
I found my own solution...
Function calls are unsupported when fetching data from a SQLite store type. It is only possible to use function calls in fetches for in-memory data. In order to still use this predicate I had to first fetch the data set and then filter the array using:
[allItems filteredArrayUsingPredicate:predicate];
Hope this helps someone!
Following code worked out just well in my case:
now() - 2592000