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

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.

Related

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

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?

Joining tables in Core Data

I have been trying to solve the following problem using Core Data without any luck.
My model has two entities:Group and Element. Both with a "name" attribute and a to-many relationship in the form of: "group.elements" and "element.groups" (a Element belonging to several Groups and a Group having several Elements)
I want to establish a "filter" in the form of:
elements that belongs to "group_A" AND "group_B"
In order to show to the user something like:
The elements that match the filter belong to this set of groups in that quantity
As an example, having something like:
Element_1 Group_A, Group_B, Group_C
Element_2 Group_B, Group_C
Element_3 Group_A, Group_B, Group_D
Element_4 Group_A, Group_B, Group_D
Element_5 Group_C, Group_D
The answer should be: Element_1, Element_3 and Element_4 match the filter and the information to be shown would be like:
Group_A has 3 elementsGroup_B has 3 elementsGroup_C has 1 elementGroup_D has 2 elementsThat match the filter
How could I put this in Core Data NSExpression, NSPredicate etc.?
Thanks.
UPDATE
I think I found two ways of solving this.
Option 1
This option establishes an NSArray with the "group's names filter" and returns all the groups with the number of elements they have that match the condition EVEN THOUGH it's zero (no elements match)
There are two entities, "Grp" and "Elem", with a to-many relationship between them.
NSError *error = nil;
// Properties to be fetched
NSPropertyDescription *namePropDesc = [[[[self.moModel entitiesByName] objectForKey:#"Grp"] propertiesByName] objectForKey:#"name"];
// Variable group filter
NSArray *grpFilter = [NSArray arrayWithObjects:#"group_A", #"group_B", nil];
// Expression for counting elements
NSExpressionDescription *countExprDesc = [[NSExpressionDescription alloc] init];
[countExprDesc setExpression:[NSExpression expressionWithFormat:#"SUBQUERY(elems,$elem, SUBQUERY($elem.grps, $grp, $grp.name in %#).#count==%d).#count", grpFilter, grpFilter.count]];
[countExprDesc setExpressionResultType:NSInteger32AttributeType];
[countExprDesc setName:#"elementCount"];
// Create data fetching and set its properties
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Grp"];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:[NSArray arrayWithObjects:namePropDesc,countExprDesc, nil]];
NSArray *results = [self.moContext executeFetchRequest:request error:&error];
NSLog(#"results = %#"results);
Option 2
This option establishes an NSArray with the "group's names filter" and returns all the groups with the number of elements they have that match the condition WITHOUT THOSE groups that don't have any elements.
In this case, I created three entities, Grp, Elem and RGE. Having RGE as an intermediate entity that keeps the to-many relationships with the two other. This option allows to put some extra information in the group-element association (creation date, etc.) if needed. Grp and Elem don't have a relationship between each other.
Note: In fact, I neened to create a "regular" field (name) in the RGE entity to apply the #count function. If a "to-many relationship field" is used it fails to count properly.
NSError *error = nil;
// Variable group filter
NSArray *grpFilter = [NSArray arrayWithObjects:#"group_A", #"group_B", nil];
// Create variable predicate string from "group's names filter"
NSMutableString *predicateStr = [[NSMutableString alloc] init];
for(int n=0;n<grpFilter.count;n++) {
if(n>0) {
[predicateStr appendString:#" AND "];
}
[predicateStr appendString:#"(ANY elem.rges.grp.name=%#)"];
}
// Filter to be applied
NSPredicate *filterQuery = [NSPredicate predicateWithFormat:predicateStr argumentArray:grpFilter];
// Expression for counting elements
NSExpressionDescription *countExprDesc = [[NSExpressionDescription alloc] init];
[countExprDesc setExpression:[NSExpression expressionWithFormat:#"name.#count"]];
[countExprDesc setExpressionResultType:NSInteger64AttributeType];
[countExprDesc setName:#"count"];
// Expression for grouping JUST by the group's name
NSExpressionDescription *grpNameExprDesc = [[NSExpressionDescription alloc] init];
[grpNameExprDesc setExpression:[NSExpression expressionWithFormat:#"grp.name"]];
[grpNameExprDesc setExpressionResultType:NSStringAttributeType];
[grpNameExprDesc setName:#"grpName"];
// THIS COULD JUST BE an NSPropertyDescription if you want the WHOLE "grp":
NSPropertyDescription *grpPropDesc = [[[[self.moModel entitiesByName] objectForKey:#"RGE"] propertiesByName] objectForKey:#"grp"];
// Create data fetching and set its properties
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"RGE"];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToGroupBy:[NSArray arrayWithObjects: grpNameExprDesc, nil]];
[request setPropertiesToFetch:[NSArray arrayWithObjects:grpNameExprDesc, countExprDesc, nil]];
[request setPredicate:filterQuery];
NSArray *results = [self.moContext executeFetchRequest:request error:&error];
NSLog(#"results = %#",results);
I had a similar problem and I solved it using the "option 2" approach: Grouping three entities with many-to-many relationships
Regards.
Pedro Ventura.
I fetched all the groups, and for the display I did the following:
Group *gr = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSSet *filtered = [NSSet set];
if ([gr.name isEqualToString:#"GroupA"]) {
filtered = [gr.elements filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"any groups.name == %#", #"GroupB"]];
}
else if ([gr.name isEqualToString:#"GroupB"]) {
filtered = [gr.elements filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"any groups.name == %#", #"GroupA"]];
}
else {
filtered = [gr.elements filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:
#"any groups.name == %# && any groups.name == %#",
#"GroupA", #"GroupB"]];
}
cell.textLabel.text =
[NSString stringWithFormat:#"%# has %d elements.",
gr.name, filtered.count] ;
This gives the correct counts as in your example. However, if there is an excluded group, it would still be listed with 0 elements.

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.

Core Data: Count across relationships

I have three models:
+----------+ +--------------+ +------+
| Occasion | <--->> | GiftOccasion | <--->> | Gift |
+----------+ +--------------+ +------+
I've like to get the name of the occasion, and a count of all the gifts that meet certain criteria. Currently I'm getting the distinct list of occasions sorted by year and name:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request retain];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Occasion" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:[NSArray arrayWithObjects:#"name", #"year", nil]];
[request setReturnsDistinctResults:YES];
NSSortDescriptor *yearDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"year" ascending:NO selector: #selector(caseInsensitiveCompare:)] autorelease];
NSSortDescriptor *nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector: #selector(caseInsensitiveCompare:)] autorelease];
NSArray *descriptors = [NSArray arrayWithObjects:yearDescriptor, nameDescriptor, nil];
[request setSortDescriptors:descriptors];
NSError *error = nil;
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
... but I'm totally stumped as to how to get the count of Gift. Is there a straightforward way, or do I need to traverse each GiftOccasion and related Gifts and add it all together?
ETA: Note that I'm generating distinct results, here, not getting the model directly. I assume I'd have to run another request to get this information, but if it's possible not to, that would be better.
Thanks!
Get number of objects in a Core Data relationship
This isn't a good answer, and I'm not going to select it as the "right" answer, but what I ended up doing was a ridiculous set of nested NSEnumerators that go through each relationship and eventually do a count += 1;, something like this:
NSUInteger giftCount = 0;
NSEnumerator *oEnum = [resultsList objectEnumerator];
Occasion *occasion = nil;
while (occasion = [oEnum nextObject]) {
NSEnumerator *goEnum = [occasion.giftOccasions objectEnumerator];
GiftOccasion *go;
while (go = [goEnum nextObject]) {
if (go.gifts) {
NSEnumerator *giftEnum = [go.gifts objectEnumerator];
Gift *gift = nil;
while (gift = [giftEnum nextObject]) {
NSLog(#"Gift: %# %#", gift.name, gift.purchased);
if ([gift.purchased boolValue] == NO) {
giftCount += 1;
}
}
}
}
}
Embarrassing, but it works. :-)
If there is a better, more efficient way to do this, I'd love to hear it. It's one of the things I dislike about Core Data as an ORM vs., say, ActiveRecord.

Core-Data: Print contents of an entity

How would I print the contents of an entity e.g. customer?
I want the data to be table like, e.g. the entity data should print like so:
First Name | Last Name | Telephone Number | Email | DOB
I also need to apply a search predicate before printing the data, e.g. print members born after 1984
Could anybody tell me how I should do this?
Thanks!
I found this link which helped me alot using core data:
http://iphoneinaction.manning.com/iphone_in_action/2009/09/core-data-part-3-retrieving-data.html
Cast the entity to an array, loop the array and NSLog the data for each result. E.g.:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"news"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"url=%#",theUrl];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
for (news *theNews in items) {
NSLog(#"Title: %#, Date: %#, News: %#", [theNews title], [theNews date], [theNews news]);
}
[fetchRequest release];