NSPredicate with Multiple parameters - xcode

Hello Need help with predicate. The problem is that I want to have one function which will fetch from database depends on the multiple values it receives, but the values don't always exist, does it mean I have to create several different predicates?
Code below
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND item_category LIKE %# AND item_make LIKE %# AND item_gender LIKE %# || item_price>%# || item_price<%#",
brand_one, brand_two, brand_three, brand_four, brand_five, categoryString, makeString, genderString,[NSNumber numberWithInt:price_bottom],[NSNumber numberWithInt:price_top]];
brand_one, brand_two and etc sometimes exist and sometimes don't.
And how should it for item_gender for example. Let's if there is no gender specified than to have both of them.
Sorry if my description of the problem confusing.
Base on Gobras comments the following code was produces
NSArray *brands = [NSArray arrayWithObjects:brand_one, brand_two, brand_three, brand_four, brand_five, nil];
NSPredicate *predicate_brands = [NSPredicate predicateWithFormat:#"brand_id like %#" argumentArray:brands];
Logical explanation of what what search function should
fetchrequest
predicate on fetch request
predicate based on 5 brand ids, item id, item category, item make, item gender,
if any of above is empty it should fetch all related entries for example if item category is empty it should fetch all "Jewellery, Watches and etc" but limit it with the rest of the predicate expressions.
Should I create for example compound predicates for brands and than create another one for item category with values "item_category like Jewellery" and "item_category like Watches" and after that how do I exactly bring them together?

Assemble an appropriate collection of individual predicates into NSCompoundPredicate
as in
NSMutableArray *parr = [NSMutableArray array];
if([brand_one length]) {
[parr addObject:[NSPredicate predicateWithFormat:#"brand_id LIKE %#",myBrandId]];
}
if([brand_two length]) {
// etc
}
NSPredicate *compoundpred = [NSCompoundPredicate andPredicateWithSubpredicates:parr];
You can stack up your predicates with orPredicateWithSubpredicates: and andPredicateWithSubpredicates: and NSCompoundPredicate being a NSPredicate itself can be compounded.
See
https://developer.apple.com/documentation/foundation/nscompoundpredicate

For those who are interested in Swift!
let arrPred = [NSPredicate(format: "yourAttribute LIKE %#", toCompareID), NSPredicate(format: "yourAttribute2 LIKE %#", toCompareID2)] //Add as many predicates you want in short make your query
let compoundpred:NSPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: arrPred)
var arrFiltered = yourArray.filteredArrayUsingPredicate(compoundpred)

It's pretty simple to build the predicate dynamically: combine your predicate format string with desired number of conditions and build corresponding NSMutableArray with the arguments. [NSPredicate predicateWithFormat:argumentArray:] will do the rest.

Related

Collection operator for summing a NSDictionary's values

Is there a simple way using KVO and collection operators, that I can sum the total value of a NSDictionary like { ID: NSNumber }?
Example:
#{
"my_ID_abcd": #(8),
"my_ID_efgh": #(2),
"my_ID_ijkl": #(3)
}
Would give 13 as a result.
Indeed #count doesn't return what I want, and #sum doesn't work here.. allValues.#sum.self neither...
I need to check the sum in a NSPredicate...
Thanks!
A way to do that would be to use a SUBQUERY(,,) & FUNCTION(,):
NSDictionary *dict = #{ #"my_ID_abcd": #(8),
#"my_ID_efgh": #(2),
#"my_ID_ijkl": #(3)};
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(FUNCTION(SELF, 'allValues'), $idsValues, $idsValues = $idsValues).#sum.integerValue = %d", value];
BOOL pass = [predicate evaluateWithObject:dict];
From what I tried, you can't really chain two operators (ie #sum & #allValues, so I used a SUBQUERY:
SUBQUERY(allValues, $idsAllValues, $idsAllValues = $idsAllValues)
Since, you need a "true/false" on the last one, I used a simple comparaison against itself.
And I used FUNCTION to build the array of values.
That way, in the end, I can call #sum on it.
I'm not very familiar with SUBQUERY & FUNCTION, maybe the call could be simplified.
If you could use predicateWithBlock:, it would be much simpler though:
NSPredicate *withBlock = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
NSInteger sum = [[[evaluatedObject allValues] valueForKeyPath:#"#sum.self"] integerValue];
return sum == value;
}];

CloudKit Query with modificationDate Always Returns No Results

My goal is to get User records from CloudKit I specify in an array of CKRecord.IDs, but only if they were updated more recently than my last updated date which I track locally.
I'm doing the following CloudKit query:
var predicate:NSPredicate
if let lastChecked = defaults.object(forKey: "lastUserFetch") as? Date{
//Subsequent fetches
predicate = NSPredicate(format: "modificationDate > %# AND recordID IN %#", argumentArray: [lastChecked, userRecordIDs])
}else{
//First fetch
predicate = NSPredicate(format: "recordID IN %#", argumentArray: [userRecordIDs])
}
let query = CKQuery(recordType: "User", predicate: predicate)
let operation = CKQueryOperation(query: query)
...
I initially set lastUserFetch to nil when my app launches, and the "First fetch" part of the query succeeds. I get all the user records back.
But once I set my lastUserFetch date after the query is done:
defaults.set(Date(), forKey: "lastUserFetch")
I get 0 records returned when the modificationDate > %# portion is included. I, of course, modify records in the CloudKit Dashboard, and I can see that their modificationDate has updated and is newer than my lastUserFetch date, but they still don't get returned in my search results.
How do I combine a modificationDate comparison with a recordID IN query?
Use an NSCompoundPredicate:
var predicate:NSPredicate
//Every fetch
predicate = NSPredicate(format: "recordID IN %#", argumentArray: [userRecordIDs])
if let lastChecked = defaults.object(forKey: "lastUserFetch") as? Date{
//Subsequent fetches
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, NSPredicate(format: "modificationDate > %#", argumentArray: [lastChecked]))
}
let query = CKQuery(recordType: "User", predicate: predicate)
let operation = CKQueryOperation(query: query)
...
Make sure you have queryable index created on modificationDate field on cloudKit

Empty results on cloudkit text search

I have a number of records in my cloudKit public database and I would like to return those with text matching a given 'searchTerm' text string.
If I use this predicate I get all of the records (up to the maximum limit)
let myPredicate = NSPredicate(value: true) // returns records
When I try to just return records containing a partial search term I get nothing
let myPredicate = NSPredicate(format: "allTokens TOKENMATCHES[cdl] %#", searchTerm) // - empty
let myPredicate = NSPredicate(format: "self contains %#", "H")// empty
let myPredicate = NSPredicate(format: "allTokens TOKENMATCHES[cdl] %#", searchTerm) // empty
let myPredicate = NSPredicate(format: "self contains 'H'")// empty
But when I search for a complete word the search works
let myPredicate = NSPredicate(format: "self contains 'Honda'") // returns matching records
Is there a way to do a partial search eg wild cards or similar?
This is a repeat of a qaestion I asked 5 years ago(!)

NSPredicate: More AND condition after ANY is possible?

[NSPredicate predicateWithFormat:#"ANY (spaceVariableDomains.domain.name = %# AND spaceVariableDomains.variable.val = %#)", ...];
I want to write something like above into NSPredicate, but it is unable to parse the format string. Is it possible somehow compound conditions after ANY? Do I need to use SUBQUERY?
Finally I used SUBQUERY approach, basically this expression helped me:
SUBQUERY(spaceVariableDomains, $x, $x.domain.name = 'xxx' and $x.variable.val = 'yyy').#count > 0 AND SUBQUERY(spaceVariableDomains, $x, $x.domain.name = 'zzz' and $x.variable.val = 'www').#count > 0

NSPredicateEditor and relationships

I've seen that every predicate that works in a query with a relationship contains at the start the words ANY or ALL (ie: ANY tags.name LIKE[c] "car"), the fact is, if I remove it (ie: tags.name LIKE[c] "car"), the result is wrong or I get a message like this one : Can't do regex matching on object.
Since i'm using an NSPredicateEditor their is no ANY or ALL that starts my query, so it always fail.
The Predicates returned is always like the second exemple (no ANY or ALL).
Do I have to subclass the NSPredicateRowTemplateEditor, in order to add myself the ANY or ALL in my predicate, or is their another way?
Same thing with the dates... my dates are saved in this format: YYYY-MM-DD HH:mm:ss, but the NSPredicateEditor use DD/MM/YYYY, so each time I try a date comparaison, it does not work. Do I also have to subclass the RowEditor, in order to change the date format?
Thank you.
Here you go:
class RowTemplateRelationshipAny: NSPredicateEditorRowTemplate {
override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate{
let predicate: NSComparisonPredicate = super.predicate(withSubpredicates: subpredicates) as! NSComparisonPredicate
let newPredicate = NSComparisonPredicate(leftExpression: predicate.leftExpression, rightExpression: predicate.rightExpression, modifier: .any, type: predicate.predicateOperatorType, options: predicate.options)
return newPredicate
}
}
class RowTemplateRelationshipAll: NSPredicateEditorRowTemplate {
override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate{
let predicate: NSComparisonPredicate = super.predicate(withSubpredicates: subpredicates) as! NSComparisonPredicate
let newPredicate = NSComparisonPredicate(leftExpression: predicate.leftExpression, rightExpression: predicate.rightExpression, modifier: .all, type: predicate.predicateOperatorType, options: predicate.options)
return newPredicate
}
}
Just change your row template class in IB to RowTemplateRelationshipAny or RowTemplateRelationshipAll.

Resources