PFQuery with multiple constraints on one key - ios

i have parse.com database. i have column in a table named "text".
i have to find text with multiple keys matching with AND condition.
i tried it all ways. i tried to use:
PFQuery *query=[PFQuery queryWithClassName:#"post"];
[query whereKey:#"text" containedIn:Array];
or
PFQuery *query=[PFQuery queryWithClassName:#"post"];
[query whereKey:#"text" containsAllObjectsInArray:Array];
or
PFQuery *query=[PFQuery queryWithClassName:#"post"];
for (NSString *str in filtersArray) {
[query whereKey:#"text" containsString:str];
}
but no one works. please guide me if parse sdks supports this or not totally? if yes how can i achieve the results.
many thanks in advance :)
EIDT:
for example i have three entries in database as text:
"i have a data entry"
"i can not find something in data"
"how can i do this"
if pass "i" and "this" it should return entry (3)
if pass "i" and "data" it should return entry (1,2)
if pass "i" and "else" it should return nothing

The reason your query doesn't work is because Parse does not support having the same constraint (in this case 'containsString:') more than once on the same key.
So, what I would suggest is to query for a regular expression which will match all of your filter strings using - (instancetype)whereKey:(NSString *)key matchesRegex:(NSString *)regex.
NSArray *qryStrings = #[str1, str2, str3, ....]; //strings you are trying to match
//string which will hold our regular expression
NSString *regexString = #"^"; //start off the regex
for (NSString *str in qryStrings) { //build the regex one filter at a time
regexString = [NSString stringWithFormat:#"%#(?=.*%#)", regexString, str];
}
regexString = [NSString stringWithFormat:#"%#.*$", regexString]; //finish off the regex
PFQuery *query = [PFQuery queryWithClassName:#"post"];
[query whereKey:#"text" matchesRegex:regexString];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
//objects array contains matching rows with matching strings
}
else {
NSLog(#"%# %#", error, [error userInfo]);
}
}];

Depending on the speed requirements of your query and the number of objects being queried, I would try separating the results using componentsSeparatedByString: after enumerating through the results array using something like: for(NSString *string in results). After separating each string into an individual array, use an if(...) statement that determines if that collection of words contains each word searched.
Code example:
NSMutableArray *searchResults = [[NSMutableArray alloc] init];
PFQuery *query=[PFQuery queryWithClassName:#"post"];
// consider setting a limit, depending on the number of posts
// consider sorting the query as needed
NSArray *posts = [query findObjects];
for(NSString *text in posts)
{
NSArray *words = [text componentsSeparatedByString:#" "]; // use SeperatedByCharactersInSet: for . , or anything else you'd need
switch ([filterArray count])
{
case 1:
if([words containsObject:filterArray[0]])
{
[searchResults addObject:text];
}
break;
case 2:
if([words containsObject:filterArray[0]] &&
[words containsObject:filterArray[1]])
{
[searchResults addObject:text];
}
break;
// case n:
// if([words containsObject:filterArray[0]] &&
// [words containsObject:filterArray[1]] &&
// [words containsObject:filterArray[n]])
// {
// [searchResults addObject:text];
// }
//
// break;
default:
break;
}
_filteredResults = [NSArray arrayWithArray:searchResults]; // local global instance of results - use for maintaining order (be sure to clear it between searches)
The _filteredResults array should be what you're wanting. Be sure to consider speed and the character separators used in the search though.

You simply just need to use whereKey:containsString:
And if you want to use additional parameters, you just simply add a subQuery

Your solution need to modify a bit.
Like this.
PFQuery *query=[PFQuery queryWithClassName:#"post"];
[query whereKey:#"text" containedIn:Array];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
//what your logic you want to write there might comes some records that are counts two times so you can remove those using NSSet
}];

Related

Why is my NSMutable not being populated correctly when inserting objects from another NSArray?

I have a PFQuery that gets the current participants of a particular event:
PFQuery *getcurrentparticipants = [PFQuery queryWithClassName:#"Event"];
[getcurrentparticipants selectKeys:#[#"Participants"]];
[getcurrentparticipants whereKey:#"objectId" equalTo:ObjectID];
[getcurrentparticipants findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSMutableArray *newParticipantsArray = [[NSMutableArray alloc]init];
if([objects[0] valueForKey:#"Participants"] == nil){ // If object retrieved in objects is null. If there are 0 participants
[newParticipantsArray addObject:PFUser.currentUser.username];
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query getObjectInBackgroundWithId:self.ObjectID
block:^(PFObject *Event, NSError *error) {
Event[#"Participants"] = newParticipantsArray;
[Event incrementKey:#"Vacants" byAmount:[NSNumber numberWithInt:-1]];
[Event saveInBackground];
}];
}else{ // STEP 5
for(int i=0;i<objects.count;i++) {
[newParticipantsArray addObject:[[objects objectAtIndex:i] valueForKey:#"Participants"]];
}
[newParticipantsArray addObject:PFUser.currentUser.username];
NSLog(#"Part to upload %#", newParticipantsArray);
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query getObjectInBackgroundWithId:self.ObjectID
block:^(PFObject *Event, NSError *error) {
Event[#"Participants"] = newParticipantsArray;
[Event incrementKey:#"Vacants" byAmount:[NSNumber numberWithInt:-1]];
[Event saveInBackground];
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
This is how the method works:
Create a PFQuery object
Query the Participants Class for an specific ObjectId
If no error, then we create a NSMutable array
If no participants are in Parse then we insert the current user as participant.
Else, insert all participants in the mutable array and add currentuser at the end of the array.
Then upload it again to Parse
My problem is in step 5:
When I perform the tasks in the else, the column in Parse looks like this :
[["Participant 1"],"Participant 2"]
But I would like to have it like this:
["Participant 1","Participant 2"]
What I have tried:
I tried things like putting the arrays like this. [newParticipantsArray addObject:[[objects objectAtIndex:i] valueForKey:#"Participants"]]; and similar combinations, of course without luck.
It’s hard to say for sure since I can’t see the structure of your data but are you sure the value held in
[[objects objectAtIndex:i] valueForKey: #“Participants”]
Is a single user and not itself an array of users? The plural key “participants” seems to suggest it’s an array of users which would also explain the result you’re getting.
If in fact the value returned for the "Participants" key is an array, you can add the objects in it to your mutable array by doing the following:
NSArray* participants = [[objects objectAtIndex:i] valueForKey:#"Participants"]
[newParticipantsArray addObjectsInArray:participants];
This uses the addObjectsInArray: method of NSMutableArray to add the objects from the old array into the new one.

What is the way to add Parse objects into a NSMutableArray?

I have a mutable array named "eventnameArray". What i want is to add objects. This objects to add come from a Parse query.
This is the mutable array:
eventArray = [[NSMutableArray alloc]init];
This is the Parse query, which works.
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query selectKeys:#[#"EventName"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %lu scores.", (unsigned long)objects.count);
// Do something with the found objects
[eventnameArray addObject:[objects valueForKey:#"EventName"]];
NSLog(#"%#", eventnameArray);
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
I suspect the problem is in the addObject call, which is not being performed correctly, or maybe in the whole PFQuery structure.
To show the array in the tableviewcell, i do this:
cell.label.text = [self.eventnameArray objectAtIndex: [indexPath row]];
EDIT :
I replaced addObject for addObjectsFromArray like this:
[eventnameArray addObjectsFromArray:[objects valueForKey:#"EventName"]];
But i still can't get it to work.
EDIT 2:
I replaced EDIT 1 with :
for (NSObject *object in objects){
NSString *name = [object valueForKey:#"EventName"];
[eventnameArray addObject:name];
}
But the label still doesn't show anything.
EDIT 3 :
The solution was just a matter or reloading data like this:
[self.tableView reloadData];
I guess what you're looking for is to replace addObject: with addObjectsFromArray: because parse returns an array to you so calling valueForKey:#"EventName" will return an array and you want to add all of those names to your eventArray.
So:
[eventnameArray addObjectsFromArray:[objects valueForKey:#"EventName"]];
You probably want to add the EventName of each object in the objects array to your eventnameArray, not the "EventName" value of the objects array.
Try this instead:
for (NSObject *object in objects){
NSString *name = [object valueForKey:#"EventName"];
[eventnameArray addObject:name];
}
//EDIT: Addition for a complete fix. Reload the tabeview afterwards.
[self.tableView reloadData];

Set PFQuery order

I want to make my PFQuery come in a random order, so the next time I'm creating the same PFQuery with limit it won't return the same objects as the first one.
PFQuery *query = [PFUser query];
[query orderBy...]; //Is there a randomOrder method?
//Or a workaround to get random order?
[query setLimit:10];
I need this to be in a random order every time, or else the PFQuery will contain the same 10 objects everytime
You can't change the ordering of data returned in the query, but you can use paging to change the first object that is returned - so you could do something like this (it is based on the ToDo sample code from Parse but it will work for any object) -
PFQuery *query =[PFQuery queryWithClassName:#"Todo"];
NSInteger count=[query countObjects];
NSInteger skip = arc4random_uniform(count-10);
query.skip=skip;
query.limit=10;
NSArray *results=[query findObjects];
NSLog(#"object count=%d",results.count);
for (PFObject *object in results) {
NSLog(#"text=%#",object[#"text"]);
}
You can now retrieve your 10 objects. for any given skip count they will be in the same order, but you could randomise the order after you retrieved the 10 items. Simply put them into an NSMutableArray and use technique in this answer - Re-arrange NSArray/MSMutableArray in random order
Note that this code isn't optimal as it doesn't perform the fetch tasks on the background thread. To use background threads you would use something like the following -
PFQuery *query =[PFQuery queryWithClassName:#"Todo"];
[query countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
query.skip=arc4random_uniform(number-10);;
query.limit=10;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"An error occurred - %#",error.localizedDescription);
}
else {
NSLog(#"object count=%d",objects.count);
for (PFObject *object in objects) {
NSLog(#"text=%#",object[#"text"]);
}
}
}];
}];
PFQuery does not support random order but you can workaround this by creating an increasing index field to each object
Then given that you know the maxIndexin the table you can generate random indices as the following:
- (NSArray *)generateRandomIndices:(int)maxIndex limit:(int)limit {
NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:limit];
for (int i=0; i<limit; i++) {
int randomIndex = arc4random() % maxIndex;
[indices addObject:[NSNumber numberWithInt:randomIndex]];
}
return indices;
}
Now you can query your class by using INpredicate
NSArray *randomIndices = [self generateRandomIndices:maxIndex limit:10];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"index IN %#", randomIndices];
PFQuery *query = [PFQuery queryWithClassName:#"className" predicate:predicate];
PFQuery don't give random objects. You can get all objects then randomize to get any 10 objects from it and show it.

How to fetch a PFObject with all it's fields and related objects in one go?

I have an NSArray of PFObjects and want to fetch all data related to the objects in this array in one go, so afterwards there is no need to make a new call to parse. How can I do this?
My answer is assuming the array you want is contained in a PFObject. You can query for this object and use the include key to include the array contained within that key.
PFQuery *query = [PFQuery queryWithClassName:#"<object's class name>"];
[query whereKey:#"objectId" equalTo:object.objectId];
[query includeKey:#"<array key>"];
If the objects in your array have pointers to other objects within them you can use the dot syntax to fetch everything at once.
[query includeKey#"<array key>.<pointer in object from array key>"];
Run the query once set up and you should retrieve an array of one object since objectIds are unique, within this object will be the array.
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if(error){
// handle error
}else{
if([objects count] > 0){
PFObject *object = objects[0]; // Only one matching object to query
NSArray *array = object[#"<array key>"]; // Array you want
}
}
}];
You can use PFObject's methods family:
+(void)fetchAll:(NSArray*)objects
Check out PFObject documentation on those methods https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/fetchAll:
As far I got your query , this might be-
PFQuery *query = [PFQuery queryWithClassName:#"CLASS_NAME"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error && objects.count != 0)
{
NSLog(#"Successfully retrieved: %#", objects);
for(int i=0; i< objects.count; i--)
{
NSDictionary *dict = [objects objectAtIndex:i];
self.label = [dict objectForKey:#"PROPERTY_KEY"]; //Just example
/* Also modify this code as per you want to fetch properties of some or all */
/* Do as you want to with properties and also change key as per need of column/property you want */
}
}
}];
This way you will get array and also fetch fetch as you want.

Query on Parse relational data is not coming back ordered using orderedByAscending

I'm querying relation data on parse and I would like the objects to come back ordered by the date they were created. I've had this method work before but haven't been able to get an ordered query using relational data. The query return is in a random order. Thanks in advance! Here's my code:
PFQuery *postQuery = [PFQuery queryWithClassName:#"Post"];
[roomQuery whereKey:#"name" equalTo:self.postName];
NSError *error;
//done on main thread to have data for next query
NSArray *results = [postQuery findObjects:&error];
PFObject *post;
if ([results count]) {
post = [results objectAtIndex:0];
NSLog(#"results were found");
} else {
NSLog(#"results were not found");
}
PFRelation *commentsRelation = [#"Comments"];
[commentsRelation.query orderByAscending:#"createdAt"];
[commentsRelation.query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error Fetching Comments: %#", error);
} else {
NSArray *comments = objects;
}
I'm a little confused by your code,
you create a "postQuery", and call it, but never use any of its data.
There's also a roomQuery that never seems to have been allocated, or used.
You're querying a specific post by its name. Are you controlling its name? If not, you should use id's
what is PFRelation commentsRelation = [#"Comments"];
Probably because it's just a snippet, this stuff is dealt with elsewhere; however, for my answer, I'm assuming that your "comments" field is an array of "Comment" class objects.
Option 1:
PFQuery * postQuery = [PFQuery queryWithClassName:#"Post"];
[postQuery whereKey:#"name" equalTo:self.postName];
// again, possibly an id field would be more reliable
// [postQuery whereKey:#"objectId" equalTo:self.postId];
[postQuery includeKey:#"Comments"];
PFObject * post = [postQuery getFirstObject];// no need to download all if you just want object at [0]
// this will contain your post and all of it's comments with only one api call
// unfortunately, it's not sorted, so you would have to run a sort.
NSArray * comments = [post[#"Comments"] sortedArrayUsingComparator: ^(id obj1, id obj2) {
return [obj1[#"createdAt" compare: obj2[#"createdAt"];
}];
Option 2:
Perhaps a better option is to rework your data structure and instead of associating the comments to the post, you could associate the post to the comments (as in the parse docs)
PFQuery * postQuery = [PFQuery queryWithClassName:#"Post"];
[postQuery whereKey:#"name" equalTo:self.postName];
// again, possibly an id field would be more reliable
// [postQuery whereKey:#"objectId" equalTo:self.postId];
PFQuery * commentQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"parent" matchesQuery:postQuery]; // when creating a comment, set your post as its parent
[commentsQuery addOrderDescending:#"createdAt"]
[commentQuery findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// comments now contains the comments for myPost
}];
Both of the above solutions avoid making extra unnecessary api calls (parse charges based on calls after all!).

Resources