NSPredicate with multiple comparisons for one query string - objective-c

I was wondering if there is a way to simplify an NSPredicate that takes in a single query string for multiple comparison targets. I'm searching multiple attributes of a core data entity for the same query string. My current query looks something like this...
[NSPredicate predicateWithFormat:#"(attributeA contains[cd] %#) OR (attributeB contains[cd] %#) OR (attributeC contains[cd] %#)", searchString, searchString, searchString];
Note that this works perfectly, but it does look a bit unsightly. Especially the searchString, searchString, searchString part. Any tips on how I could possibly simplify this would be great!
thanks!

You can use NSCompoundPredicate for your OR & AND operations like this.
Obj-C - OR
// OR Condition //
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"X == 1"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"X == 2"];
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:#[predicate1, predicate2]];
Obj-C - AND
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"X == 1"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"X == 2"];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[predicate1, predicate2]];
Swift - OR
let predicate1:NSPredicate = NSPredicate(format: "X == 1")
let predicate2:NSPredicate = NSPredicate(format: "Y == 2")
let predicate:NSPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate1,predicate2] )
Swift - AND
let predicate1:NSPredicate = NSPredicate(format: "X == 1")
let predicate2:NSPredicate = NSPredicate(format: "Y == 2")
let predicate:NSPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1,predicate2] )
Swift 3 - OR
let predicate1 = NSPredicate(format: "X == 1")
let predicate2 = NSPredicate(format: "Y == 2")
let predicateCompound = NSCompoundPredicate.init(type: .or, subpredicates: [predicate1,predicate2])
Swift 3 - AND
let predicate1 = NSPredicate(format: "X == 1")
let predicate2 = NSPredicate(format: "Y == 2")
let predicateCompound = NSCompoundPredicate.init(type: .and, subpredicates: [predicate1,predicate2])

You could do:
NSPredicate *p = [NSPredicate predicateWithFormat:#"attributeA contains[cd] $A OR attributeB contains[cd] $A or attributeC contains[cd] $A"];
NSDictionary *sub = [NSDictionary dictionaryWithObject:searchString forKey:#"A"];
p = [p predicateWithSubstitutionVariables:sub];
Or you could do something weirder, like this:
- (NSPredicate *)buildOrPredicate:(NSDictionary *)stuff {
NSMutableArray *subs = [NSMutableArray array];
for (NSString *key in stuff) {
NSString *value = [stuff objectForKey:stuff];
NSPredicate *sub = [NSPredicate predicateWithFormat:#"%K contains[cd] %#", key, value];
[subs addObject:sub];
}
return [NSCompoundPredicate orPredicateWithSubpredicates:subs];
}
And then invoke that with:
NSDictionary *stuff = [NSDictionary dictionaryWithObjectsAndKeys:
searchString, #"attributeA",
searchString, #"attributeB",
searchString, #"attributeC",
nil];
NSPredicate *p = [self buildOrPredicate:stuff];
The only other thing I can think of that might work is to try using positional specifies in the predicate format. However, I don't know if the parser recognizes them the same way that +stringWithFormat: does:
NSPredicate *p = [NSPredicate predicateWithFormat:#"attributeA contains[cd] %1$# OR attributeB contains[cd] %1$# or attributeC contains[cd] %1$#", searchString];

Related

IN operator with NSPredicate and SBElementArray

Does the IN operator work for filtering SBElementArrays? I have been trying to use it but it always returns a NULL array.
My code (hexArray will typically have more elements):
SBElementArray *musicTracks = [libraryPlaylist fileTracks];
hexArray = [NSArray arrayWithObject: #"3802BF81BD1DAB10"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K IN %#",#"persistentID",hexArray];
NSLog(#"%#", [[musicTracks filteredArrayUsingPredicate:predicate] valueForKey:#"persistentID"]);
NSLog(#"%#", hexArray);
NSLog(#"%#", predicate);
Output:
2013-05-26 12:59:29.907 test[1226:403] (null)
2013-05-26 12:59:29.907 test[1226:403] (3802BF81BD1DAB10)
2013-05-26 12:59:29.908 test[1226:403] ANY persistentID IN {"3802BF81BD1DAB10"}
I have tried setting the predicate to:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K == %#",#"persistentID",hexArray];
Output:
2013-05-26 13:03:04.629 test[1258:403] (3802BF81BD1DAB10)
2013-05-26 13:03:04.630 test[1258:403] (3802BF81BD1DAB10)
2013-05-26 13:03:04.630 test[1258:403] ANY persistentID == {"3802BF81BD1DAB10"}
And this works fine. But I would like the IN functionality.
Instead of doing
persistentID IN ('abc', 'abc', 'abc', ...)
you can do
persistentID == 'abc' OR persistentID == 'abc' OR ...
It seems to work pretty fast.
NSMutableArray *subPredicates = [NSMutableArray arrayWithCapacity:persistentIDs.count];
for (NSNumber *persistentID in persistentIDs) {
[subPredicates addObject:pred(#"persistentID == %#", persistentID.hexValue)];
}
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
[tracks filterUsingPredicate:predicate];
NSLog(#"%ld", tracks.count);
Try using CONTAINS[c]
Ex:-
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %# CONTAINS[c] %k",hexArray, #"persistentID"];
I ended up just looping through all the elements of hexArray and using an equality predicate on each pass. Probably not the most efficient, but it works.
for (NSString *hexID in hexArray){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"persistentID == %#",hexID];
iTunesTrack *track = [[musicTracks filteredArrayUsingPredicate:predicate] objectAtIndex:0];
[track duplicateTo:playlist];
}
Your predicate should be %K IN %# (without the ANY), if I understand your intention correctly (get all the tracks that have one of the IDs in the array).
For some reason, this doesn't work with SBElementArray, but you could simply convert it to a regular NSArray before applying the predicate (an NSSet should work too, and might be more efficient):
SBElementArray *musicTracks = [libraryPlaylist fileTracks];
NSArray *musicTracksArray = [NSArray arrayWithArray:musicTracks];
NSArray *hexArray = [NSArray arrayWithObjects: #"CE24B292556DB1BA", #"CE24B292556DB1F0", #"CE24B292556DB1C4", nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"persistentID", hexArray];
NSLog(#"%#", [[musicTracksArray filteredArrayUsingPredicate:predicate] valueForKey:#"persistentID"]);
Scripting Bridge technically supports the IN operator, in that it will construct a properly-formed Apple event for it, but most applications don't understand it. The best workaround is the chained OR tests as suggested by NSAddict.

How to modify this predicate so it handles multiple keywords efficiently

I have this predicate:
NSPredicate * thePredicateKeyword = [NSPredicate predicateWithFormat:#"any keywords.thekeyword beginswith [cd] %#", searchTerm];
Basically each business have many to many relationship with keywords.
But suppose I do not have one searchTerm. Say I have an array.
How would I do so?
I suppose I can just make predicate for each and combine them with or predicate, etc.
However, is there a way to more efficiently do this using in keywords or stuff like that?
What about a function that returns something like this:
-(NSPredicate *)createCompoundPredicateForSearchTerms:(NSArray *)searchTerms
{
NSMutableArray *subPredicates = [[NSMutableArray alloc] init];
NSEnumerator *searchTermEnum = [searchTerms objectEnumerator];
NSString *searchTerm;
while (searchTerm = [searchTermEnum nextObject]) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"keywords.thekeyword beginswith [cd] %#", searchTerm];
[subPredicates addObject:predicate];
}
return [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
}
This is what I actually use. However, the anwer I chose is what inspire it.
NSArray * keywords = [searchTerm componentsSeparatedByString:#" "];
NSMutableArray * keywordPredicates = [NSMutableArray array];
for (NSString * aKeyword in keywords) {
NSPredicate * thePredicateKeyword = [NSPredicate predicateWithFormat:#"any keywords.thekeyword beginswith [cd] %#", aKeyword];
[keywordPredicates addObject:thePredicateKeyword];
}
NSPredicate * thePredicateKeyword = [NSCompoundPredicate orPredicateWithSubpredicates:keywordPredicates];
return thePredicateKeyword;

What could the syntax for a predicate, that should narrow down table results based on 4 textfields, look like?

So I have 4 text fields in which a user can use to narrow down table results in order to find an exact place. My problem is that since a user does not have to type something in each text field, I can't simply use AND in my predicate.
Right now I have the opposite from what I'd like my app to do. If one types in a City and a State, my predicate shows more results rather than narrowing the results. Which makes perfect sense to why its doing that.
Other than creating many if statements based on what text fields are empty or not, is there an better way around this?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(place CONTAINS[c] %#) OR (city CONTAINS[c] %#) OR (state CONTAINS[c] %#) OR (zip CONTAINS[c] %#)",
userPlace, userCity, userState, userZip];
This should work:
fetchRequest.predicate = nil;
NSMutableSting *predicateString = [NSMutableString string];
NSArray *attributeNames = #[#"name", #"city", #"state", #"zip"];
int i = 0;
for (NSString *s in #[userName, userCity, userState, userZip]) {
if (s && ![s isEqualToString:#""]) {
[predicateString appendFormat:#"%#",
predicateString.length ? #" AND " : #""];
[predicateString appendFormat:#"(%# CONTAINS[c] '%#')",
attributeNames[i], s];
}
i++;
}
if (predicateString.length) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:predicateString];
}
The "proper" way to do this is with compound predicates, but this would be even more verbose.
I also noticed a possible mismatch in your example between the attribute place and your substitution variable userName. Should it be name? userPlace?
I think this is what you want:
[NSPredicate predicateWithFormat:#"(%# = \"\" OR place CONTAINS[c] %#) AND (%# = \"\" OR city CONTAINS[c] %#) AND (%# = \"\" OR state CONTAINS[c] %#) AND (%# = \"\" OR zip CONTAINS[c] %#)",
userName, userName, userCity, userCity, userState, userState, userZip, userZip];

Multiple conditions in NSPredicate

How to use multiple conditions with NSPredicate?
I am using this but not getting anything in the returned array .
NSPredicate *placePredicate = [NSPredicate predicateWithFormat:#"place CONTAINS[cd] %# AND category CONTAINS[cd] %# AND ((dates >= %#) AND (dates <= %#)) AND ((amount >= %f) AND (amount <= %f))",placeTextField.text,selectedCategory,selectedFromDate,selectedToDate,[amountFromTextField.text floatValue],[amountToTextField.text floatValue]];
NSArray *placePredicateArray = [dataArray filteredArrayUsingPredicate:placePredicate];
NSLog(#"placePredicateArray %#", placePredicateArray);
The amount and the category can be empty sometimes. How should I construct the NSPredicate?
You can build your placesPredicate using other NSPredicate objects and the NSCompoundPredicate class
Something like :
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"place CONTAINS[cd] %#", placeTextField.text];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"category CONTAINS[cd] %#", selectedCategory];
NSPredicate *p3 = [NSPredicate predicateWithFormat:#"(dates >= %#) AND (dates <= %#)", selectedFromDate,selectedToDate];
NSPredicate *p4 = [NSPredicate predicateWithFormat:#"(amount >= %f) AND (amount <= %f)", [amountFromTextField.text floatValue],[amountToTextField.text floatValue]];
NSPredicate *placesPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[p1, p2, p3, p4]];
Now, if you are missing category for example you can just use a dummy YES predicate to replace it :
NSPredicate *p2;
if (selectedCategory) {
p2 = [NSPredicate predicateWithFormat:#"category CONTAINS[cd] %#", selectedCategory];
} else {
p2 = [NSPredicate predicateWithBool:YES]
}
I would tend to handle this one piecemeal. That is,
placePredicate = [NSPredicate predicateWithFormat:#"place CONTAINS[cd] %#",placeTextField.text];
NSMutableArray *compoundPredicateArray = [ NSMutableArray arrayWithObject: placePredicate ];
if( selectedCategory != nil ) // or however you need to test for an empty category
{
categoryPredicate = [NSPredicate predicateWithFormat:#"category CONTAINS[cd] %#",selectedCategory];
[ compoundPredicateArray addObject: categoryPredicate ];
}
// and similarly for the other elements.
Note that I don't bother even putting into the array the predicate for category (or anything else) when I know there isn't one.
// Then
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
compoundPredicateArray ];
And if I were planning to do it a lot, I wouldn't use the format method, but keep around the building blocks and just change whatever changes between uses.
Suppose a class Person with attributes "name", "age", "income"
"personArray" is array of class Person
NSPredicate *mypredicate = [NSPredicate predicateWithBlock:^BOOL(personObj Person, NSDictionary *bindings) {
NSNumber *age = personObj.age;
String *name = personObj.name;
NSNumber *income = personObj.income
BOOL result = (age > 20 && name == "Stack" && income >40000);
return result;
}];
NSArray *filteredArray = [personArray filteredArrayUsingPredicate: mypredicate];
For further you can read this article Array Filter Using Blocks
In Swift
let filterArray = personArray.filter
{ person in
let age = personObj.age;
let name = personObj.name;
let income = personObj.income
BOOL result = (age > 20 && name == "Stack" && income >40000);
return result;
}

NSPredicate concatenating attributes

I m trying to figure out how to concatenate attribute names. I have a county and a district attribute that I want to query like
[NSPredicate predicateWithFormat:#"county + district contains[cd] %#",searchBar.text]
gives me unimplemented SQL generation for predicate error. and I am not sure how to implement NSPredicate.
Thanks
This should give you an idea of how to do some more complicated searching. It will match queries for "county district", "district county", etc.
NSArray *searchTerms = [searchBar.text componentsSeparatedByString:#" "];
NSString *predicateFormat = #"(county contains[cd] %#) OR (district contains[cd] %#)";
NSPredicate *predicate;
if ([searchTerms count] == 1) {
NSString *term = [searchTerms objectAtIndex:0];
predicate = [NSPredicate predicateWithFormat:predicateFormat, term, term];
} else {
NSMutableArray *subPredicates = [NSMutableArray array];
for (NSString *term in searchTerms) {
NSPredicate *p = [NSPredicate predicateWithFormat:predicateFormat, term, term];
[subPredicates addObject:p];
}
predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
}
[fetchRequest setPredicate:predicate];
See Cocoa Is My Girlfriend: Adding iTunes-style search to your Core Data application for more explanation.
I ended up implementing another field as concatenating the two variables(district+country) and perform the query on that variable.
I did something similar and concluded that like cekisakurek, the best method was to concatenate the fields into a common field (more relevant for first/last name)
- (NSString *)fullName {
return [NSString stringWithFormat:#"%# %#", self.firstName, self.lastName];
}
and then filtered on this field using 'contains[cd]'
[NSPredicate predicateWithFormat:#"fullName contains[cd] %#", self.searchBar.text];
[NSPredicate predicateWithFormat:#"county contains[cd] %# AND district contains[cd] %#",searchBar.text,searchBar.text];
Just try the above lines of code, it would helps you.