Core data max value for property of entities children - core-data

I know I shouldn't look at Core Data as something relational. But with a SQL query it is easiest to explain what I'm trying to do.
What I'd like to do is get the max value from a property constrained to the children of an entity. In SQL something like: select max(valueY) from graphdata where parentID = 10
I'm doing something akin to the above SQL query successfully excluding the where (NSPredicate) part.
I added the NSPredicate to my code thinking it would exclude the graphdata objects not within the KPI I'm looking at. Well, guess not, it's not what I'm seeing, it seems to do nothing at all really.
The Core Data Model consistst of multiple KPI objects, each containing an Array of GraphData objects. Every GraphData object contains a valueX and a valueY property.
I want the max valueY for the graphdata of a specified KPI object.
Here's my code, it's based on one of Apple's core data snippets:
NSManagedObjectContext *context = ...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:GRAPH_DATA_ENTITY_NAME 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:#"ValueY"];
//MY WHERE KIND OF CLAUSE/PREDICATE WHICH ISN'T WORKING?
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"kpiForGraphData == %#", self.kpi];
[request setPredicate:predicate];
// Create an expression to represent the function you want to apply
NSExpression *expression = [NSExpression expressionForFunction:#"max:"
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:#"maxValueY"];
[expressionDescription setExpression:expression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType]; // For example, 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;
id requestedValue = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
requestedValue = [[objects objectAtIndex:0] valueForKey:#"maxValueY"];
}
}
[expressionDescription release];
[request release];
return [requestedValue doubleValue];
So far I've been trying to help myself by reading docs, searching through Google and stackoverflow. Everything I seem to find either selects entities (NSPredicates) or selects values using a function. Never have I seen an example or an explanation doing what I'd like to do. I like Core Data, but right now SQL seems way more straightforward to me.

Related

Saving Core Data related data and retrieving with NSPredicate and NSFetchedResultsController with multiple entities

I'm fairly new to Core Data and am still trying to understand accessing and filtering related data. My problem is either I'm not getting the data correctly into the managedObjectContext or I'm not pulling it out correctly. (I think the first, but I'm not sure.)
Here's my data model with two entities related one to many: (I plan to refactor once I get one level of relationship working.)
I have a SeasonsVC in which you click on a season name and the list of games for that season is supposed to appear in the GamesVC and you have the option to add or edit an existing game. This works fine at a first pass. I can add and edit games via this code in the GameDetailsVC:
-(IBAction)done:(UIBarButtonItem *)sender {
Game *game = nil;
if (self.gameToEdit != nil) {
game = self.gameToEdit;
NSLog(#"Hitting gametoedit");
} else {
game = [NSEntityDescription insertNewObjectForEntityForName:#"Game" inManagedObjectContext:self.managedObjectContext];
NSLog(#"Hitting new game");
}
game.opponent = self.opponentTextField.text;
//season.seasonDescription = self.seasonDescriptionTextView.text;
NSLog(#"Game to edit: %#", game.opponent);
//NSLog(#"Season: %#", season);
//NSLog(#"MOC in done: %#", self.managedObjectContext);
//NSLog(#"Season name: %#", season.seasonName);
[self dismissViewControllerAnimated:YES completion:nil];
}
I can then see the games in the GamesVC via the fetchedResultsController and delegate methods, but each game is associated with every season. Once I try to filter the data with a predicate so that I only see the games that were added for that season all the games disappear. Here's the code for that from the GamesVC:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Game" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"Season name for predicate %#", self.season.seasonName);//shows correct season name
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"season.seasonName like '%#'", self.season.seasonName]];
[fetchRequest setPredicate: predicate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dateOfGame" ascending:NO] ;
[fetchRequest setSortDescriptors:#[sortDescriptor]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root2"];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Since I can log the correct season name right before the predicate statement, I think that the added games are not getting "associated" with the correct season when I put them in the MOC in the done: method shown above; otherwise, I'd expect the predicate to find them.
Can you help this rookie? Thanks.
A day later I have this sorted. I needed to make three changes to what I was doing.
First, in my prepareForSegue in my GameListVC I needed to pass the season along to the GameDetailsVC by changing the code to this. (Just added the one line: controller.season = self.season)
//pass the Season object to be edited
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Game *gameToEdit = [self.fetchedResultsController objectAtIndexPath:indexPath];
controller.gameToEdit = gameToEdit;
controller.season = self.season;
Second, I had to put the new/edited game object into the MOC by adding these two lines to my done: method in the GameDetailsVC.
NSMutableSet *games = [self.season mutableSetValueForKey:#"games"];
[games addObject:game];
Third, I needed to simplify my predicate statement in my fetchedResultsController method in the GameListVC.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"season.seasonName like %#", self.season.seasonName];
So, to answer my own question, I was both not saving the game linked to the season and my predicate was wrong.
I hope some of this helps another rookie.

search core data without fetch all rows

I have Core Data with +2 millions of rows and i want to search for two specific fields: name & phone (for example). I insert scopes for each field in the Search Bar. Everything go fine if I haven't large data set. That's why I want to search in my core data without load all rows in memory before go search controller. Just need a result when my search text length > 3 or when the Search Button Clicked.
I have just one Table View with Search Bar
I populate when AppDidFinish with Call history plist
When the search bar isActive my App frezes until 2 millions rows loads. I need Avoid this step and move forward to step 4
Enter the search text. Then the result filteredArray has shown in the Table View
...
If have any idea I will appreciated.
Here you have some codes:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Guia" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:50];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"GuiaCache"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
abort();
}
return _fetchedResultsController;
}
Maybe in this method I need to improve with some Predicate
- (NSFetchRequest *)searchFetchRequest
{
if (_searchFetchRequest != nil) {
return _searchFetchRequest;
}
_searchFetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Guia" inManagedObjectContext:self.managedObjectContext];
[_searchFetchRequest setEntity:entity];
[_searchFetchRequest setFetchBatchSize:50];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[_searchFetchRequest setSortDescriptors:sortDescriptors];
return _searchFetchRequest;
}
Finaly the two functions to search in Core Data
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
if ([searchText length] > 3)
[self searchForText:searchText scope:_scopeKeyIndex];
}
- (void)searchForText:(NSString *)searchText scope:(NSUInteger)scopeOption
{
if (self.managedObjectContext)
{
NSString *predicateFormat = #"%K BEGINSWITH[cd] %#";
NSString *searchAttribute = #"telephone";
if (scopeOption == 1)
{
searchAttribute = #"name";
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat, searchAttribute, searchText];
[self.searchFetchRequest setPredicate:predicate];
NSError *error = nil;
_filteredList = [self.managedObjectContext executeFetchRequest:self.searchFetchRequest error:&error];
}
}
If you need some other piece of code, just comment.
First, run Instruments against your application, specifically Time Profiler. Where does it say the time is being spent?
Do you have a predicate in your search? What does the predicate look like? You need to show some code for people to help.
Doing a fetch against against 2 million records will take a bit of time but I suspect it is not loading 2 million rows into memory as that would most likely cause memory problems as well as speed problems.
Post the results from Time Profiler and lets see where the time is being spent.
Update
First your predicate is very expensive. BEGINSWITH should be avoided if at all possible. Making it both case and diacritic insensitive increases that cost dramatically.
Did you run Instruments against your code? Did you run the time profiler? Without running that you are just guessing. You NEED to run the time profiler and at least show the results if not link to the entire profile.

Core Data retrieving relationship data

I am really having trouble retrieving items that have been created through the Menu entity. This is the code I used to add an item to a specific Menu object
-(void)additem:(NSString *)entity :(NSDictionary *)aDictionary :(NSString *)menu
{
NSLog(#"additem");
NSError *error = nil;
Menu *menuItem = nil;
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Menu" inManagedObjectContext:managedObjectContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"mname=%# and msection =%#",#"Parents",#"Keydates"]];
menuItem = [[managedObjectContext executeFetchRequest:request error:&error] lastObject];
if (error) {
//Handle any errors
}
if (!menuItem) {
//Nothing there to update
NSLog(#"This class doesn't exist");
}
Items *anitem = (Items *)[NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:managedObjectContext];;
anitem.type = [aDictionary objectForKey:#"type"];
anitem.title = [aDictionary objectForKey:#"title"];
anitem.image = [aDictionary objectForKey:#"image"];
anitem.subtitle = [aDictionary objectForKey:#"subtitle"];
[menuItem addItemsObject:anitem];
[managedObjectContext save:&error];
}
I want to use a predicate to retrieve all the items that were created on a specific Menu object. Here is the code I am trying to retrieve it with.
- (void) readItems: (NSString *)section {
NSLog(#"readItems");
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Menu"
inManagedObjectContext:managedObjectContext];
fetchRequest.resultType = NSDictionaryResultType;
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"mname=%# and msection = %#",#"Parents",#"Keydates"]];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (Items *item in fetchedObjects) {
NSLog(#">>>>>>%#",item);
}
}
Can anyone point me in the right direction.I know I pass section and don't use it. I have place the actual values in.
Your readItems: method specifies the entity for your fetch as Menu yet when you execute the fetch you are expecting Item (since you start iterating through fetchedObjects and casting them as Item).
Instead what you want to do is et your entity to Item and change the predicate to
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"items.mname=%# and items.msection = %#",#"Parents",#"Keydates"]];
Furthermore you should consider renaming your relationships. The relationship field specifies exactly what you would expect to see when you follow the arrow. Thus when I look at Items and follow its arrow to Menu I would expect the name of this relationship to be menu yet you call it menuItems. You have done this correctly for the relationship from Menu to Item.
That way we would end up with a more readable predicate looking like:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"menu.mname=%# and menu.msection = %#",#"Parents",#"Keydates"]];
Happy Coding

NSFetchedResultsController with NSDictionaryResultsType in NSFetchRequest

I have been looking over this issue since a week and haven't got any solution, so I thought to make the question more generalized, may be it will help the users to look into it and give me a solution.
Scenario:
I have an expense tracking iOS Application and I have a view controller called "DashBoardViewController" (table view controller - with FRC) which would basically categorize my expenses/incomes for a given week, a month, or year and display it as the section header title for example : (Oct 1- Oct 7, 2012) and it shows expenses/incomes ROWS and related stuff according to that particular week or month or year.
My Question:
What I want to accomplish is :
Show Distinct Results based on "Category" attribute of "Money" entity and calculate "Sum" based on the attribute.
But, my view controller called "dashboard view controller" is filled with NSFetchedResultsController with section name key path as "type" that can be an expense or an income. In order to get Distinct results, I shall use Result type as NSDictionaryResultsType in my fetch request which will give me unique results but FRC fails, it doesn't work with that. So, how will I get my section names then? I have posted the code below.
EDIT - BASED ON MARTIN'S SUGGESTION
- (void)userDidSelectStartDate:(NSDate *)startDate andEndDate:(NSDate *)endDate
{
AppDelegate * applicationDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [applicationDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Money" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicateDate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", startDate, endDate];
[fetchRequest setPredicate:predicateDate];
// Edit the sort key as appropriate.
typeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"type" ascending:YES];
dateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
if(self.choiceSegmentedControl.selectedIndex == 0)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"cat" ascending:NO];
}
if(self.choiceSegmentedControl.selectedIndex == 1)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"vendor" ascending:NO];
}
if(self.choiceSegmentedControl.selectedIndex == 2)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"paidBy" ascending:NO];
}
NSArray * descriptors = [NSArray arrayWithObjects:typeSortDescriptor, dateSortDescriptor,choiceSortDescriptor, nil];
[fetchRequest setSortDescriptors:descriptors];
[fetchRequest setIncludesSubentities:YES];
NSError * anyError = nil;
NSArray *propertiesToFetch = [[NSArray alloc] initWithObjects:
[entity.propertiesByName objectForKey:#"cat"],
nil];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:propertiesToFetch];
NSArray * objects = [context executeFetchRequest:fetchRequest error:&anyError];
for (NSDictionary *d in objects)
{
NSLog(#"NSDictionary = %#", d);
}
NSSet *uniqueSet = [NSSet setWithArray:[objects valueForKey:#"cat"]];
uniqueArray = [NSMutableArray arrayWithArray:[uniqueSet allObjects]];
self.categoryArray = uniqueArray;
if(_fetchedResultsController)
{
[_fetchedResultsController release]; _fetchedResultsController = nil;
}
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"type" cacheName:nil];
//self.fetchedResultsController.delegate = self; in order to stop "change tracking"
if(![_fetchedResultsController performFetch:&anyError])
{
NSLog(#"error fetching:%#", anyError);
}
[fetchRequest release];
//Finally you tell the tableView to reload it's data, it will then ask your NEW FRC for the new data
[self.dashBoardTblView reloadData];
self.startDate = startDate;
self.endDate = endDate;
}
With this code, SECTION NAME KEY PATH is not working and it's complaining that the object will be placed in unnamed section.
A fetched results controller does not support change tracking (i.e. the FRC delegate is set) in combination with a fetch request with NSDictionaryResultType.
The reason is documented with the setIncludesPendingChanges: function:
Special Considerations
A value of YES is not supported in conjunction
with the result type NSDictionaryResultType, including calculation of
aggregate results (such as max and min). For dictionaries, the array
returned from the fetch reflects the current state in the persistent
store, and does not take into account any pending changes, insertions,
or deletions in the context.
Change tracking of the FRC implies includesPendingChanges = YES for the fetch request, and that does not work with NSDictionaryResultType.
One workaround could be to use a FRC without change tracking, so you do not set the FRC delegate. But that means that to update your table view, you have to
save the managed object context, and
call performFetch on the FRC and reloadData on the table view.
Another workaround could be to use the FRC to fetch all sections and rows without the sum aggregation, and use the results to compute new table rows with the aggregation in memory (e.g. in controllerDidChangeContent).
UPDATE: (From the discussion) Another important point is that if you use a fetch request with NSDictionaryResultType, then the sectionNameKeyPath of the fetched results controller must be included in the propertiesToFetch of the fetch request.

fetch request for entity.attribute == #"somevalue"

How do I setup a fetch request to only pull the data from an entity's attribute with one particular value? This is the basic code I've used before.
-(void)fetchResults
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:self.entityDescription.name];
NSString *cacheName = [self.entityDescription.name stringByAppendingString:#"Cache"];
// predicate code
if (self.predicate != nil) {
[fetchRequest setPredicate:self.predicate];
}
// end of predicate code
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
BOOL success;
NSError *error;
success = [self.fetchedResultsController performFetch:&error];
if (!success)
{
NSLog(#"%#", [error localizedDescription]);
}
}
I've been looking at this page: http://bit.ly/KevYwR is this the right direction?
Do I need to use NSPredicate or can I do without?
Thanks for any help, point in the right direction, etc.
Setting up a NSFetchRequest is equivalent to a SELECT statetement in SQL language.
Here a simple example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:moc]];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
// error handling code
The array results contains all the managed objects contained within the sqlite file. If you want to grab a specific object (or more objects) you need to use a predicate with that request. For example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"attribute == %#", #"Some Value"];
[request setPredicate:predicate];
In this case results contains the objects where attribute is equal to Some Value. Setting a predicate is equal to put the WHERE clause in a SQL statement.
Note
I suppose that the name of the entity is EntityName and its property is called attribute which is of type string.
For further info I suggest you to read the Core Data programming guide and NSFecthRequest class reference.
http://developer.apple.com/library/iOS/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
Hope it helps.

Resources