How do you construct the predicate for NSFetchRequest setHavingPredicate:? - ios

For the life of me I can not seem to get this to work.
Assume our entity is an managed object with a status field and an order field.
How would I go about getting all orderedEntries having more than one order that are the same?
Please no answers telling me to just do a subquery with #count in the main predicate, since I know of that solution, the point of this post is to understand how to use the having predicate in core data, which would probably be faster than a subquery anyways. (unless you explain why I can not use a having clause)
The following code would return an array of dictionaries with the number of orders per order number. What I want is to be able to add a having clause to restrict my request to only return the dictionaries representing objects of those orders that have a count greater than 1.
Here is the code so far and my attempts at a having predicate:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"OrderedEntry"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSDictionaryResultType];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(status == %#)",[NSNumber numberWithInt:EntryStatusAlive]];
[fetchRequest setPredicate:predicate];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: #"order"]; // Does not really matter
NSExpression *maxExpression = [NSExpression expressionForFunction: #"count:"
arguments: [NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"orderCount"];
[expressionDescription setExpression: maxExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
[fetchRequest setPropertiesToFetch: [NSArray arrayWithObjects:expressionDescription,#"order",nil]];
[fetchRequest setPropertiesToGroupBy:[NSArray arrayWithObjects:#"order",nil]];
//[fetchRequest setHavingPredicate:[NSPredicate predicateWithFormat:#"#count > 1"]];
//[fetchRequest setHavingPredicate:[NSComparisonPredicate predicateWithLeftExpression:maxExpression rightExpression:[NSExpression expressionForConstantValue:[NSNumber numberWithInteger:1]] modifier:NSDirectPredicateModifier type:NSGreaterThanPredicateOperatorType options:NSCaseInsensitivePredicateOption]];
NSError *error;
NSArray * array = [context executeFetchRequest:fetchRequest error:&error];

I ended up going with this for anyone interested
-(BOOL)ordersAreSaneOnDay:(NSNumber*)dayNumber forUser:(User*)user inContext:(NSManagedObjectContext*)context {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"BasicEntry"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSDictionaryResultType];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(status == %#) && ((type != %#) && (type != %#) && (dayNumber == %#)) && ((user == NIL) || (user == %#))",[NSNumber numberWithInt:EntryStatusAlive],[NSNumber numberWithInt:EntryTypeTask],[NSNumber numberWithInt:EntryTypeCompletedTask],dayNumber,user];
[fetchRequest setPredicate:predicate];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: #"order"]; // Does not really matter
NSExpression *maxExpression = [NSExpression expressionForFunction: #"count:"
arguments: [NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"orderCount"];
[expressionDescription setExpression: maxExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
[fetchRequest setPropertiesToFetch: [NSArray arrayWithObjects:expressionDescription,#"order",nil]];
[expressionDescription release];
[fetchRequest setPropertiesToGroupBy:[NSArray arrayWithObjects:#"order",nil]];
//[fetchRequest setHavingPredicate:[NSPredicate predicateWithFormat:#"self.order.#count > 1"]];
//[fetchRequest setHavingPredicate:[NSComparisonPredicate predicateWithLeftExpression:maxExpression rightExpression:[NSExpression expressionForConstantValue:[NSNumber numberWithInteger:1]] modifier:NSDirectPredicateModifier type:NSGreaterThanPredicateOperatorType options:NSCaseInsensitivePredicateOption]];
NSError *error;
NSArray * array = [context executeFetchRequest:fetchRequest error:&error];
array = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"orderCount > 1"]];
//NSLog(#"it worked %#",array);
[fetchRequest release];
if ([array count]) return FALSE;
return TRUE;
}

I've got this working using the following:
[fetchRequest setHavingPredicate:[NSPredicate predicateWithFormat:#"$orderCount > 1"]];
Use the name of your expressionDecription as variable $orderCount.
Alternatively you can use
NSExpression *countExpression = [NSExpression expressionForVariable:#"orderCount"];
[fetchRequest setHavingPredicate:[NSPredicate predicateWithFormat:#"%# > 1", countExpression]];

Firstly, whenever I try something analogous to what you're doing, I get an error that you can't pass a to-many relationship to setPropertiesToFetch:. The NSFetchRequest documentation backs this up: "The property descriptions may represent attributes, to-one relationships, or expressions." So that's problem #1.
Problem #2 is that it appears that you can't group by a to-many relationship either (this isn't made clear in the documentation, but you get the same error and it also makes sense).
Remove "order" from the properties to fetch. Group by an attribute. Modify your main predicate to only include those attributes you group by (or just remove it). Specify "order" in your having predicate.
[fetchRequest setPropertiesToGroupBy: #[ #"???" ]];
[fetchRequest setHavingPredicate: [NSPredicate predicateWithFormat: #"self.order.#count > 1"]];
Now you'll see the request will work, but the results probably weren't what you were expecting:
- NSArray
- NSDictionary { status = #"alive", orderCount = "4" }
- NSDictionary { status = #"alive", orderCount = "9" }
- NSDictionary { status = #"alive", orderCount = "2" }
- etc...
NSDictionaryResultType doesn't actually give you anything to identify those objects by - it just gives you the values.
So your next step is to get back IDs for those OrderedEntry objects. The trick is to include an expression which will give you back the NSManagedObjectID as a key in each dictionary.
I don't know if this will actually give you improved performance at all (over just AND-ing it in to the main predicate). In my experience, one of the best things you can do to improve fetching performance is to create singleton NSPredicates and use substitution variables to set up each fetch. Predicate parsing can be expensive.
Dictionary result types can be a pain. Usually just constructing a good predicate is better. I tend only to use them for expressions (i.e. performing statistic-type calculations on the graph which return a value). If you look at all the restrictions around properties to fetch and group by and the predicate restrictions, this seems to be what Apple intend it for. I'm not even sure it's possible to do what you want to do by grouping and using a having predicate - for example, what are you going to group by? If by status (which you need to group by to include in your predicate), won't that collapse all the OrderEntries and you won't get the separate objectIDs and orderCounts? It's best to stick to the main predicate for this, not grouping and having predicates.

Related

Query max NSDate in core data

Is there a way to query NSDate in CoreData. For example if I want an entity with the highest NSDate value? I see that NSExpression "max:" only takes an NSNumber.
You can actually ask SQL for just that value, not the object with that value:
NSExpression *date = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDate = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:maxDate]];
NSExpressionDescription *d = [[[NSExpressionDescription alloc] init] autorelease];
[d setName:#"maxDate"];
[d setExpression:maxSalaryExpression];
[d setExpressionResultType:NSDateAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:d]];
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
} else {
if (0 < [objects count]) {
NSLog(#"Maximum date: %#", [[objects objectAtIndex:0] valueForKey:#"maxDate"]);
}
}
more detail under Fetching Managed Objects -> Fetching Specific Values in the CoreData documentation.
or
Perfomed a query, ordered on Date field DESCENDING, and using setFetchLim it:1.
Its not perfect, but at least it worked.
You can do this directly in SQLite-- without fetching everything and then filtering the result, and without the complexity of NSExpression.
To get the one object that has the max date, do something like (assuming entity name Entity and date attribute timeStamp):
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timeStamp = self.#max.timeStamp"];
fetchRequest.predicate = predicate;
Do the fetch. You'll get (at most) one result, which will be the instance with the max date.
If you want to get just the date and not the entire managed object, add this before doing the fetch:
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = #[ #"timeStamp" ];
you can get it with
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"tablename"];
fetchRequest.fetchLimit = 1;
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"yourDate" ascending:NO]];
NSError *error = nil;
id person = [managedObjectContext executeFetchRequest:fetchRequest error:&error].firstObject;

Retrieve sum of filtered records from core data and display results to a label

I want to query my entity "DIncome" to sum the attribute "credit" But I only want entires that have their "daystamp" attribute equal to today. The following code gets me close but I don't understand how to finish this code in order to output the sum of credit DIncome attribute to a label on my view controller.
Heres what I got;
//Get today date
NSDate *dateSelect = [NSDate date];
//formate today date as day only "dd"
NSDateFormatter *dfdd = [[NSDateFormatter alloc]init];
[dfdd setDateFormat:#"dd"];
NSString *formattedDatedd = [dfdd stringFromDate:dateSelect];
//Fetchrequest
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DIncome" 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:#"credit"];
NSExpression *sumExpression = [NSExpression expressionForFunction:#"sum:" 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:#"sumCredit"];
[expressionDescription setExpression:sumExpression];
[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 = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
NSLog(#"Credit Sum: %#", [[objects objectAtIndex:0] valueForKey:#"sumCredit"]);
self.dayDisplayLabel.text = sumExpression;
}
}
For the second part of the question I need to set the sum of the credit to my dayDisplayLabel
This is not working because its an incompatible pointer assigning NSString to NSExpression
self.dayDisplayLabel.text = sumExpression;
How do I make this right?
You can use [request setResultType:NSDictionaryResultType] with propertiesToFetch to retrieve only certain attributes of your managed objects. If you want to limit the recorded records, you can use NSPredicate (acts like the WHERE clause in a SQL statement).
Fetch requests return their results as arrays of managed objects. You can get attribute values from those objects. In your case you get an array of managed objects representing the DIncome entity type. If you subclassed NSManagedObject then these are instances of your subclass; if you didn't subclass then they're instances of NSManagedObject.
To get values of string attributes from these instances, use key-value coding. If your attribute were named "title", you'd get the string value of the first object with:
NSString *firstTitle = [mutableFetchResults[0] valueForKey:#"title"];
Get the values of any other attributes using similar code.
If you want the fetch request to only include a subset of the instances of DIncome, look into NSPredicate. You can use this like a SQL "WHERE" clause to request only instances matching specific criteria, e.g.:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", someNameString];
[request setPredicate:predicate];
In order to filter your query results you need to setPredicate for your request like so
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"daystamp = %#", formattedDatedd];
[request setPredicate:predicate];
And to display this to your label, use the same line you are using in the NSLog line.
[[objects objectAtIndex:0] valueForKey:#"sumCredit"]
if would look like this,
self.dayDisplayLabel.text = [[objects objectAtIndex:0] valueForKey:#"sumCredit"];
Hope this helps!

Retrieving count of a core data relation

I searched high and low but I couldn't find exactly what I was looking for. My question is similar to this, but slightly different:
Core Data - Count of Related Records
Let's say I have a Car entity which has a one to many relation with a Person entity. This means that the car could have multiple people driving it, but each person drives only one car.
I want to be able to execute only one predicate wherein I could achieve the following:
All cars which are 'red'.
Return only the 'Year' and 'Color' attributes of the matching car.
Return a count of how many people are driving this car (i.e the size of the NSSet of People inside each resulting Car).
Is it possible to do all this with one query?
I know how to do this with multiple queries. I would just use setPropertiesToFetch and use a filtered predicate to achieve 1 and 2 above. I would then perform another count query (countForFetchRequest) on the Persons entity for every car to find how many Person(s) drive each car.
The key is the 3rd requirement above. I want to do everything in one predicate and I don't want to bring all of the Person entity objects into memory (performance) on the initial query. Furthermore it hurts to call another countForFetchRequest query for each car.
What's the best way to do this?
Thanks!
I cannot test this at the moment, but that should be possible by adding the following expression description to the "properties to fetch":
NSExpression *countExpression = [NSExpression expressionForFunction: #"count:" arguments: [NSArray arrayWithObject:[NSExpression expressionForKeyPath: #"drivers"]]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"driversCount"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
Return only 'red' cars:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"color LIKE 'red'"];
Return a count of how many people are driving this car:
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"people"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:"
arguments:#[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"count"];
[expressionDescription setExpression:countExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
Return only the 'year' and 'color' attributes (and the count):
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car"
inManagedObjectContext:context];
NSDictionary *attributes = [entity attributesByName];
NSArray *properties = #[expressionDescription, attributes[#"year"], attributes[#"color"]];
Build and execute the fetch request:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:properties]; // return only count, year & color
[request setPredicate:predicate]; // return only red cars
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
Process the results:
if (results) {
for (NSDictionary *result in results) {
NSLog(#"Year: %#", result[#"year"]);
NSLog(#"Color: %#", result[#"color"]);
NSLog(#"Drivers: %#", result[#"count"]);
}
}
else {
NSLog(#"Error: %#", error);
}

Getting a maximum value from a core data relationship

I search the net for answers to this problem, but unfortunately I don't think I am fluent enough in core data procedures to actually include the right combo of keywords.
I have two entities... Users and Bookmarks with a one-to-many relationship.
Users : string:firstName, string:lastname, string:iconImage
with a relationship to Bookmarks - inverse
Bookmarks : string:title, string:url, string:content, image:Binary Data, order:Integer32
with a relationship to Users - inverse
My goal here is to query the bookmarks for a particular user and find the highest number for the key:order, so that when I add an new bookmark, it will be one larger than the max. I have seen the Apple example, which works and makes sense, but I need a little more. The example returns the maximum value for all records in that entity.
-(NSNumber*) getNextBookmarksOrderForUser:(NSManagedObjectID*)userID
{
NSNumber *highOrder;
Users *user =[self getUserByID:userID];
if (user)
{
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"order"];
NSExpression *highestOrderingNumber = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc]init];
[expressionDescription setName:#"maxOrdering"];
[expressionDescription setExpression:highestOrderingNumber];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Bookmarks" inManagedObjectContext:[_dataContext managedObjectContext]];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
[request setResultType:NSDictionaryResultType];
NSError *error = nil;
NSArray *objects = [[_dataContext managedObjectContext] executeFetchRequest:request error:&error];
if(objects == nil) {
// Handle the error
}
else {
if ([objects count] > 0) {
highOrder = [NSNumber numberWithInt:[[[objects objectAtIndex:0] valueForKey:#"maxOrdering"]intValue]];
NSLog(#"Highest ordering number: %#", highOrder);
}
}
return highOrder;
}
return nil;
}
So if I have two users, one with 10 bookmark and the other with 25, the above code will always return 25. I tried adding a predicate:
NSPredicate *byUser = [NSPredicate predicateWithFormat:#"self == %#", user];
[request setPredicate:byUser];
Does anyone have any suggestions? I Know that I could return all bookmark for a user and the write the code to find my value, but I would like to do it the most efficient way.
Thank you for any help.
From my comment
I think you should use a predicate like this [NSPredicate predicateWithFormat:#"users == %#", user]; Since you are querying against Bookmarks.

NSPredicate, get results with a subset of one-to-many relationship

I'mm working around with Core Data and NSFetchedResultsController.
My Data Model looks like this:
Product with one-to-many relationship called dataLines.
The dataLine entity has a property name theWeek.
I want to fetch all Product where dataLines.theWeek == someValue. This is easily done with a subquery. But this returns all dataLines. Is it possible to create a NSPredicate that returns the Product and a subset if dataLines only with the dataLines == someValue?
What you want to achieve could be reached in two ways:
using a SUBQUERY
[NSPredicate predicateWithFormat:#"SUBQUERY(dataLines, $x, $x.theWeek == %#).#count > 0)", [NSNumber numberWithInt:18]];
or the ANY modifier
[NSPredicate predicateWithFormat:#"ANY dataLines.theWeek == %#", [NSNumber numberWithInt:18]];
You can do also the following if you need to check against multiple values:
[NSPredicate predicateWithFormat:#"SUBQUERY(dataLines, $x, $x.theWeek == %# or $x.theWeek == %#).#count > 0)", [NSNumber numberWithInt:18], [NSNumber numberWithInt:19]];
The same can be applied to ANY modifier. ANY ... OR ANY ....
Maybe if you share some code we could help you.
P.S. I suppose you don't use scalar values and theWeek is a number.
Hope it helps.
You should fetch the dataLine property instead.
Assuming your Product and dataLine entity connected by relationship someRelation then you can try this code;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityWithName:#"dataLine" inManagedObjectContext:self.managedObjectContext]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"dataLines.week == %#",theWeek]];
NSMutableArray *tmpProduct [[NSMutableArray init] alloc];
NSMutableArray *tmpArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (dataLine *theDataLine in tmpArray);
NSLog(#"%#",theDataLine.someRelation.name);
tmpProduct = theDataLine.someRelation.name;
then you can just call tmpProduct to call or display your product in table view
Create a fetch request for the 'Product' entity:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName:#"Product" ...]]
then create a predicate using the properties/attributes of Product with 'ANY':
[fetchRequest setPredicate:
[NSPredicate predicateWithFormat:#"ANY dataLines.theWeek == %#", <whatever week>]];
then execute the fetch to get an array of Product with at least one <whatever week>.
Generally see 'Fetching Managed Objects', NSPredicate and related documentation.

Resources