NSPredicate always gives back the same data - objective-c

I am trying to work with NSPredicates. But it always give me back the same array. Here you can see my predicate.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"whichAlbum.album_id == %d", AlbumId];
[request setEntity:[NSEntityDescription entityForName:#"Picture" inManagedObjectContext:self.genkDatabase.managedObjectContext]];
[request setPredicate:predicate];
Also when I try it hardcoded. It gives back the same array.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"whichAlbum.album_id == 5"];
My database model is:
And here you can see how I put data in my database for entity Picture.
+ (Picture *)pictureWithGenkInfo:(NSDictionary *)genkInfo
inManagedObjectContext:(NSManagedObjectContext *)context
withAlbumId:(int)albumId
withPictureId:(int)pictureId;
{
Picture *picture = nil;
picture = [NSEntityDescription insertNewObjectForEntityForName:#"Picture"
inManagedObjectContext:context];
picture.url = [genkInfo objectForKey:PICTURES_URL];
picture.pic_album_id = [NSNumber numberWithInt:albumId];
picture.picture_id = [NSNumber numberWithInt:pictureId];
return picture;
}
Anybody can help me ?
Kind regards
EDIT
for (NSDictionary *genkInfo in albums ) {
albumId++;
Album *album = [Album albumWithGenkInfo:genkInfo inManagedObjectContext:document.managedObjectContext withAlbumId:albumId];
for (NSDictionary *genkInfo2 in pictures ) {
pictureId++;
Picture *pic = [Picture pictureWithGenkInfo:genkInfo2 inManagedObjectContext:document.managedObjectContext withAlbumId:albumId withPictureId:pictureId];
[album addPicturesObject:pic]; // this method should be automatically generated
}
pictureId = 0;
// table will automatically update due to NSFetchedResultsController's observing of the NSMOC
}

Better, assuming the value for AlbumId is some kind of number primitive:
old style:
[NSPredicate predicateWithFormat:#"whichAlbum == %#", [NSNumber numberWithInt:AlbumId]];
modern style: (xcode 4.5)
[NSPredicate predicateWithFormat:#"whichAlbum == %#", #(albumId)];
As it looks like predicateWithFormat: might only generate proper predicates with %# and #K in its format strings.
Best: Assuming you have access to the Album managed object you are trying to match try this:
[NSPredicate predicateWithFormat:#"whichAlbum == %#", album];
Match to the object, not one of its properties

just remove whichAlbum and try:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"album_id == %d", AlbumId];

Related

Filtering many-to-many count expression using existing subquery

In my app, I have a many-to-many relationship between tags and links as follows :
Tags <<-->> Links
I am trying to return a list of the tags that relate to links that have the currently active tags, but are not included in the active tags.
I also want to obtain a count of the number of links that have the 'other' tags, which needs to be limited by the active tags.
Using the below, I have been able to return the 'other' tags and a count of links, but the count returned is of all links for each tag.
I would like to be able to count the links using a similar approach to the one I'm using to build the subquery, but am struggling to get it to work. I have tried using the subquery generated in the count NSExpression, but this errors when the subquery is evaluated.
// Test array of tag names
self.activeTagArray = [#[#"tag1", #"tag2"] mutableCopy];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[Tag entityName]];
// We want to exclude the tags that are already active
NSPredicate *activeTagsPredicate = [NSPredicate predicateWithFormat:#"NOT ANY name IN %#", self.activeTagArray];
// Build subquery string to identify links that have all of the active tags in their tag set
NSString __block *subquery = #"SUBQUERY(links, $link, ";
[self.activeTagArray enumerateObjectsUsingBlock:^(id tagName, NSUInteger index, BOOL *stop) {
if (index == self.activeTagArray.count - 1) {
subquery = [subquery stringByAppendingString:[NSString stringWithFormat:#"SUBQUERY($link.tags, $tag, $tag.name = '%#') != NULL", tagName]];
} else {
subquery = [subquery stringByAppendingString:[NSString stringWithFormat:#"SUBQUERY($link.tags, $tag, $tag.name = '%#') != NULL AND ", tagName]];
}
}];
subquery = [subquery stringByAppendingString:#") != NULL"];
NSLog(#"Subquery : %#", subquery);
NSPredicate *noTagsPredicate = [NSPredicate predicateWithFormat:subquery];
// Create a predicate array
NSArray *predicateArray = #[noTagsPredicate, activeTagsPredicate, userPredicate];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];
fetchRequest.predicate = compoundPredicate;
fetchRequest.relationshipKeyPathsForPrefetching = #[#"links"];
// Set up the count expression
NSExpression *countExpression = [NSExpression expressionForFunction: #"count:" arguments:#[[NSExpression expressionForKeyPath: #"links.href"]]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
expressionDescription.name = #"counter";
expressionDescription.expression = countExpression;
expressionDescription.expressionResultType = NSInteger32AttributeType;
fetchRequest.propertiesToFetch = #[#"name", expressionDescription];
// Sort by the tag name
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetchRequest.sortDescriptors = #[sortDescriptor];
fetchRequest.resultType = NSDictionaryResultType;
NSError *error = nil;
NSArray *resultsArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"Error : %#", [error localizedDescription]);
}
NSMutableArray *allTags = [[NSMutableArray alloc] init];
for (NSDictionary *tagDict in resultsArray) {
NSLog(#"Tag name : %#, Link Count : %#", tagDict[#"name"], tagDict[#"counter"]);
[allTags addObject:tagDict[#"name"]];
}
[allTags addObjectsFromArray:self.activeTagArray];
Any help with this would be greatly appreciated!
If I understand your question correctly, the following predicate fetches your "other tags",
i.e. all tags that are related to a link which is related to all of the given "active tags":
NSArray *activeTags = #[#"tag1", #"tag2"];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Tag"];
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"NOT name in %#", activeTags];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"SUBQUERY(links, $l, SUBQUERY($l.tags, $t, $t.name IN %#).#count = %d).#count > 0",
activeTags, [activeTags count]];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[p1, p2]];
[request setPredicate:predicate];
And now the "trick": The left hand side of the predicate p2 is
SUBQUERY(links, $l, SUBQUERY($l.tags, $t, $t.name IN %#).#count = %d).#count
and that is exactly the count of links that should be included in the result,
so we can create the expression description from that predicate:
NSExpression *countExpression = [(NSComparisonPredicate *)p2 leftExpression];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
expressionDescription.name = #"counter";
expressionDescription.expression = countExpression;
expressionDescription.expressionResultType = NSInteger32AttributeType;
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"name", expressionDescription]];
The resulting SQLite query is quite complex. It might be sensible to fetch the links first:
NSFetchRequest *linkRequest = [NSFetchRequest fetchRequestWithEntityName:#"Link"];
NSPredicate *linkPredicate = [NSPredicate predicateWithFormat:#"SUBQUERY(tags, $t, $t.name IN %#).#count = %d",
activeTags, [activeTags count]];
[linkRequest setPredicate:linkPredicate];
NSArray *activeLinks = [context executeFetchRequest:linkRequest error:&error];
and fetch the tags in a separate step, which can be done with above code where only the
predicate p2 is replace by the simpler subquery
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"SUBQUERY(links, $l, $l IN %#).#count > 0", activeLinks];

NSPredicate for NSNumber property of NSManagedObject

I have NSNumber * year property of NSManagedObject, it's type in data model is Integer 16.
I try to check with NSPredicate for this year, but can't find the right one.
What I tried:
NSPredicate *p = nil;
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
NSNumber *yearNo = [nf numberFromString:term];
if (yearNo) {
p = [NSPredicate predicateWithFormat:#"(cars.year == %i)", yearNo.intValue];
}
I also tried:
NSPredicate *p = nil;
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
NSNumber *yearNo = [nf numberFromString:term];
if (yearNo) {
p = [NSPredicate predicateWithFormat:#"(cars.year == %#)", yearNo];
}
In both cases app crashes.
If you provide more details for your model, we could help you.
But I think the problem is due to cars. If cars is to-many you need a modifier for this
[NSPredicate predicateWithFormat:#"ANY cars.year == %#", yearNo];
As #flexaddicted already said, you have not supplied sufficient information, e.g. for which entity the fetch request is made.
If you want to fetch Car objects with a given year, the predicate is just
[NSPredicate predicateWithFormat:#"year == %#", yearNo]

search by date goes SIGABRT

I'm working with this entity that has a timeStamp attribute that happens to be a Date type
Problem comes out, when I try to perform a search by date like:
NSDate *today = [NSDate date];
NSDate *end_day = [today dateByAddingTimeInterval: -5*one_day]; // the last 5 days
result = [helper fetchObjectsForEntityName:#"MyEntity" withPredicate:
[NSPredicate predicateWithFormat:#"timestamp > %f", [end_day ] ]];
When I print out the value of the predicate, all I get is something like:
timestamp > CAST(378132723.717909, "NSDate")
But when the statement gets executed, it crashes.
I use the same method in other parts of the code and it work with no problems, and
obviously if I remove the NSPredicate, again it works.
For the question's sake here's also the code for fetchObjectsForEnittyName: WithPredicate:
- (NSSet *)fetchObjectsForEntityName:(NSString *)newEntityName
withPredicate:(id)stringOrPredicate, ...
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:newEntityName];
if (stringOrPredicate)
{
NSPredicate *predicate;
if ([stringOrPredicate isKindOfClass:[NSString class]])
{
va_list variadicArguments;
va_start(variadicArguments, stringOrPredicate);
predicate = [NSPredicate predicateWithFormat:stringOrPredicate
arguments:variadicArguments];
va_end(variadicArguments);
} else {
NSAssert2([stringOrPredicate isKindOfClass:[NSPredicate class]],
#"Second parameter passed to %s is of unexpected class %#",
sel_getName(_cmd), #"Whatevah");
predicate = (NSPredicate *)stringOrPredicate;
}
[request setPredicate:predicate];
}
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error != nil)
{
[NSException raise:NSGenericException format:[error description]];
}
return [NSSet setWithArray:results];
}
Does anyone have a clue about what the issue might be?
First thing is that you're comparing object with scalar and have some extra brackets, try this instead:
[NSPredicate predicateWithFormat:#"timestamp > %#", end_day];
second thing to try is format specifier for attribute like:
[NSPredicate predicateWithFormat:#"%K > %#", #"timestamp", end_day];
the complex the question seem the easiest the answer is.
i was using
[NSPredicate predicateWithFormat:#"timestamp > %#", end_day];
without the capital S, like described in my entity!
so it should have been
[NSPredicate predicateWithFormat:#"timeStamp > %#", end_day];
case solved.

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.

How to sort NSPredicate

I am trying to sort my array while using NSPredicate.. I have read other places that possibly using NSSortDescriptor could be an option. Having some trouble figuring this out.
I am attempting to sort my array by companyName.
Any advice appreciated, thanks
Greg
- (void)filterSummaries:(NSMutableArray *)all byNameThenBooth:(NSString*) text results:(NSMutableArray *)results
{
[results removeAllObjects];
if ((nil != text) && (0 < [text length])) {
if ((all != nil) && (0 < [all count])) {
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"companyName contains[cd] %# OR boothNumber beginswith %#", text, text];
[results addObjectsFromArray:[all filteredArrayUsingPredicate:predicate]];
}
}
else {
[results addObjectsFromArray:all];
}
}
you have several options how to sort an array:
I'll show a NSSortDescriptor-based approach here.
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"companyName contains[cd] %# OR boothNumber beginswith %#",
text,
text];
// commented out old starting point :)
//[results addObjectsFromArray:[all filteredArrayUsingPredicate:predicate]];
// create a descriptor
// this assumes that the results are Key-Value-accessible
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"companyName"
ascending:YES];
//
NSArray *results = [[all filteredArrayUsingPredicate:predicate]
sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]];
// the results var points to a NSArray object which contents are sorted ascending by companyName key
This should do your job.
The filteredArrayUsingPredicate: function walks through your array and copies all objects that match the predicate into a new array and returns it. It does not provide any sorting whatsoever. It's more of a search.
Use the sorting functions of NSArray, namely sortedArrayUsingComparator:, sortedArrayUsingDescriptors:, sortedArrayUsingFunction:context: and the like, whichever serves you most.
Checkout NSArray Class Reference for details.
BTW: If you want to sort lexically, you may use sortedArrayUsingSelector:#selector(compare:) which will use NSString's compare: function to find the right order.