Using NSPredicate for efficient PFQuery - ios

My app is using Parse as a back end. At some point in the app, i have to query objects that should contain at least one of the tags in another array. To make it clear, in the database i have a class Pictures which has a Tags property that saves tags in an array. In my app, i have an array of tags that i need to compare with. Only the objects that has at least one of the tags stored in the Tags property should be fetched. I tried to using the following PFQuery functions whereKey:containedIn: and whereKey:containsAllObjectsInArray: however none of them does what i want. Now i shifted my attention to NSPredicates. I tried the following:
PFQuery *postsQuery = [PFQuery queryWithClassName:#"Pictures" predicate:[NSPredicate predicateWithFormat:#"ANY Tags MATCHES ANY ", _tags]];
When i run the code i receive the following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "ANY Tags MATCHES ANY "'
How can i perform such a query or the right NSPredicate for my situation?
As an illustration on what the query should be about for further explanation:
let's say one of the objects in the database has the following in the "Tags" column
Tags = ["music", "video", "picture", "chocolate"]
and the tag array that i want to use in predicate is the following:
tagsArray = #["picture", "French"];
what i'm expecting is receiving the previous object because it has the "picture" tag. If another object doesn't contain at least one of the tags in the tagsArray it shouldn't be fetched.
Edite:
I found out that i can't use Aggregate Operations such as ANY, SOME, ALL, or NONE. could i find another solution?

The other solution is to use cloud code and write the query on the server in Javascript.
Then you just access the new API endpoint that you defined and get the results of the query that way.

Okay, i found out that using whereKey:ContainedIn: actually works out in this case. My problem that i was passing by mistake an array of dictionaries. The one i wanted to use is made of strings.

This is an example of how to use NSPredicate
in swift 4
let predicate1 = "\(PFUser.current()!.objectId!)\(userObj.objectId!)"
let predicate2 = "\(userObj.objectId!)\(PFUser.current()!.objectId!)"
let predicate = NSPredicate(format:"\(CHATS_ID) = '\(predicate1)' OR \(CHATS_ID) = '\(predicate2)' ")
let query = PFQuery(className: CLASS_NAME, predicate: predicate)

Related

Is it possible to include a [NSPredicate predicateWithBlock:] in a NSCompoundPredicate? [duplicate]

I am playing with an app that uses Core Data and NSManagedObjects to populate a UITableView. There is only one class in my application, called Event. I have created the following custom instance method on Event:
- (BOOL)isExpired {
return ([[self.endOn dateAtEndOfDay] timeIntervalSinceNow] < 0);
}
I would like to limit the UITableView that displays Event objects to only the Events that are expired - that is, where isExpired returns YES. I have tried to do this by adding an NSPredicate to the NSFetchRequest:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {return([evaluatedObject isExpired]);}];
[fetchRequest setPredicate:predicate];
but I get the error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Problem with subpredicate BLOCKPREDICATE(0x272ac)'
***. Does this mean that you can't use a block predicate with an NSFetchRequest? Or have I just constructed it improperly?
Thank you!
So, it appears that we've established in the comments to the original post that this is likely caused by SQLite stores being incompatible with block predicates, since Core Data cannot translate these to SQL to run them in the store (thanks, JoostK).
There might be a couple of ways to overcome this:
Provided that the end date of your entities is a regular attribute, you might be able to express the expiry constraint as a predicate format string instead of a block predicate, which Core Data should be able to translate into a SQL clause.
If the above is possible, you will probably prefer to use a fetch request template to retrieve the expired items. You would need to pass in a substitution variable like $NOW to give access to the current date, though. This has the advantage of making the predicate template show up in the model editor.
Both approaches, however, have the disadvantage of duplicating existing functionality (i.e., your isExpired method). So another way would be fetch all qualifiying entities regardless of their expiry state first, and then run a dedicated filtering step on the resulting set of entities to weed out the non-expired ones. Since by that point, they have been fully resurrected from the store, you should be able to use a block predicate for this.
You can do a normal fetch request without specifying the predicate, and afterwards filter the resulting array:
NSArray *allEvents = [context executeFetchRequest:fetchRequest];
if (!allEvents) { // do error handling here
}
NSArray *expiredEvents = [allEvents filteredArrayUsingPredicate:predicate];

FetchRequest with NSPredicate not finding results?

I am storing data in a table that has the columns "name" and "series". I am using this NSPredicate to query:
var request : NSFetchRequest = NSFetchRequest(entityName: "Entry")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "name = %# AND series = %#", name, series)
return request
The 'name' and 'series' variables are passed in as String arguments, which are also the data types on the table.
For some reason, this query returns no data. If I do a fetch request on this table without specifying a predicate I can see that the data is indeed there. I am not sure what I'm doing wrong.
For what it's worth I have tried enclosing the conditionals in parens but that didn't seem to make a difference either. Thanks in advance!
UPDATE: I've tried many different things but so far nothing is working. I'm really stumped.
For what it's worth it seems like I am having the same issue as this person: NSPredicate Returns No Results with Fetch Request, Works with Array Filtering
But, there isn't anything on that entry stating specifically what the solution to the problem was.
You can check if the data is there by
printing the URL of the application documents directory to the console.
going to this directory in Terminal
running sqlite3 <databaseName>
trying select * from z<entityName> where name = '<nameData>'
You will be able to explore the data and check if it contains what you expect. Don't forget to also check the content of your name and series variables in the code.
Oh wow...thank you everyone so much for all your answers. In the end it turned out that when I was populating the id for inserting a new row into the table, I was looking at a different table to calculate the new primary key id.
So I was overwriting existing records, which is why my query kept failing.
Kids, if you copy/paste your code without rigorously checking it, you're gonna have a bad time.

Filter a Realm database by matching on tags

I'm using Realm Swift. I have a database full of Document objects, each of which has a List of Tag objects stored as a property. Given one or more tags, how can I get the set of Document objects that match?
My first thought was to filter with an NSPredicate SUBQUERY, but it appears that isn't supported yet.
I also figured I could maintain a list on each Tag object of all of the Document objects that have been labelled with it, and then join the lists of each selected Tag. But I don't see a way to do that in Realm either.
Your realm model should be like this,
class Document:Object{
let tags = List<Tag>()
//...
}
and then you can filter documents by tag using ANY keyword,
let realm = Realm()
var documentsFilteredByTag = realm.objects(Document).filter("ANY tags.id = '\(tagID)'")

Search in array of different objects

I'm struggling with my predicate search here, probably a lack of vocabulary.
I have a (kind of) weak design here that my deadline doesn't allow me to change too deeply, and the situation is the following.
I'm searching in a tableview of contacts AND users, my two objects of concern here. The search works fine when there are only contacts (and none of the objects listed are users).
Now if it so happens (which it will, very often) that some of these contacts are users, when I search the tableview, my predicate key doesn't match, and I obviously get an exception.
How could I proceed to go around this, knowing I would like the search to still include everyone. (I have a backup plan where I just remove the users from the search feature and it works like a charm).
I tried using the OR in my predicate, like so :
#"compositeName contains[c] %# OR name contains[c] %#" //(where %# is my search string)
but it's not enough to skip the fact that my contact has a "compositeName" and my user has a "name", which causes an exception.
I don't want to modify the key name of my users to "compositeName" because it would imply to reinstall the app for all my beta testers, which is (due to deadlines and app-generated content) also not possible. Unless there is a way for them to have the new data model without having to reinstall, then I would do that and simply call them all "compositeName". (or "name").
I'm using core data and the array is full of NSManagedObject (of user & contact types).
Any idea?
EDIT 1 : Is this a good idea?
I'm thinking of splitting the arrays if there are users, then using different predicates in both, then finally combine them again and show those results. But it seems like a lot of processing and maybe there is something more efficient?
EDIT 2: Crash log
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: the entity User is not key value coding-compliant for the key "compositeName".'
EDIT 3 : Class check
According to comments and other posts on stack, you can actually add a condition check, and also do class checks in predicate. Now I tried to combine both by writing this :
#"((className == %#) name contains[c] %#) OR ((className == %#) compositeName contains[c] %#)", #"User",searchText, #"Contact", searchText
which isn't a correct format. This is the first time i'm playing with predicates so if you have any clue how to write a predicate that says
(If class = User, then name contains [c], if class = Contact, then compositeName contains [c])
Your issue is that your combined predicate is going to send valueForKey for both of the properties to each object - and a given object only supports 1 of the two properties, so you get the exception.
If you combine an object class test using an AND with the name/compositeName test you can avoid this issue - by testing the class first, the predicate will skip the second test (for name/compositeName) because the first test has already returned false - making it pointless checking the second clause of the AND.
You can use a predicate something like
#"(className == %# AND name contains[c] %#) OR (className == %# AND compositeName contains[c] %#)",[User className],searchString,[Contact classname],searchString

'NSInvalidArgumentException', reason: 'Unsupported predicate (null)'

I am getting this Unsupported predicate error, while trying to instantiate a
NSFetchedResultsController with an aggregate Predicate.
My Coredata structure looks like this - There is a PurchaseDetails Entity which has n number of Payment Entity (One to many relationship). Each Payment has a type, for eg.Cash, Cheque , Other etc. Now I need to get the list of all the PurchaseDetails where Payment.type = Cash.
The NSPredicate looks like this: #"ALL payments.type = 'cash'"
Are we not supposed to use ALL ?
The predicate isn't crashing on creation rather it crash while executing the fetch request.
But the same fetch request works fine when used separately without using a NSFetchedResultsController. They both wont work together ?
Yes you need to use ALL if payments is a to-many relationship. Note that this means that every single payment's type will be #"cash". If you just want at least one cash payment, use ANY.
The (null) in the error message hints at something else though. Did you attach the predicate to the fetch request?
Also, did you try to set cache:nilin your fetched results controller? The FRC could be the reason you get stale results.

Resources