Searching for Parse objects where an object's array property contains a specified string - ios

I have a PFObject subclass which stores an array of strings as one of its properties. I would like to query for all objects of this class where one or more of these strings start with a provided substring.
An example might help:
I have a Person class which stores a firstName and lastName. I would like to submit a PFQuery that searches for Person objects that match on name. Specifically, a person should be be considered a match if if any ‘component’ of either the first or last name start with the provided search term.
For example, the name "Mary Beth Smith-Jones" should be considered a match for beth and bet, but not eth.
To assist with this, I have a beforeSave trigger for the Person class that breaks down the person's first and last names into separate components (and also lowercases them). This means that my "Mary Beth Smith-Jones" record looks like this:
firstName: “Mary Beth”
lastName: “Smith-Jones”
searchTerms: [“mary”, “beth”, “smith”, “jones”]
The closest I can get is to use whereKey:EqualTo which will actually return matches when run against an array:
let query = Person.query()
query?.whereKey(“searchTerms”, equalTo: “beth”)
query?.findObjectsInBackgroundWithBlock({ (places, error) -> Void in
//Mary Beth is retuned successfully
})
However, this only matches on full string equality; query?.whereKey(“searchTerms”, equalTo: “bet”) does not return the record in question.
I suppose I could explode the names and store all possible sequential components as search terms (b,e,t,h,be,et,th,bet,etc,beth, etc) but that is far from scalable.
Any suggestions for pulling these records from Parse? I am open to changing my approach if necessary.

Have you tried whereKey:hasPrefix: for this? I am not sure if this can be used on array values.
https://parse.com/docs/ios/guide#queries-queries-on-string-values

Related

Return children of Realm RLMResults<Object> instead of <Object>'s when filtering

Example Realm relationship:
People.Dogs.FavouriteFoods
that are strictly one way -> RLMArrays
I have:
let result = RLMResult<People> from a previous operation.
and I have an array of FavouriteFood.IDs that a user selected
let selectedIDs: [String]
Now I am trying to filter/predicate this result, but instead of returning People, which I already have, I am trying to get out the FavouriteFood objects that intersect with the selectedIDs I can only find guides that explain how to sort/filter on RLMResults<People> where the result is People i.e. the same as the generic type on RLMResult.
My goal is to, in the end, construct a list where I can say "Out of the 14 FavouriteFoods Person A's Dogs have, 7 of them are in the selectedIDs list" etc. for Person B, C, D...
I want something like: "ANY dogs.favouriteFoods.ID in selectedIDs" but it should return all the FavouriteFoods matching the predicate for an individual Person, instead of all the People having Dogs having these particular favouriteFoods.
Is this possible to do as a predicate? Is there a way to flip the concept to ask for FavouriteFoods instead, or must I loop over all people, dogs, favouriteFoods and manually tally this up?
Thanks for any help given.

Convert string into multidimensional array in Neo4J

I loaded data from csv into Neo4J. One column in the file is an array of arrays that Neo4J now considers one large string. How can I convert it back into an array?
My file looks like this:
Id, name, reviews
1, item1, "[[date1, User1, Rating1],
[date2, User2, Rating2],
[date3, User3, Rating3]] "
Into Neo4J:
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///data/file.csv" AS line
CREATE(:Product{
Id:toInteger(line.Id),
name:toString(line.name),
reviews:line.reviews})
RETURN line
Now the review column is loaded, but considered one large string.
"[date1, User1, Rating1], [date2, User2, Rating2], [date3, User3, Rating3]"
Is there any way to split it into two arrays like this:
First Array:
[date1, User1, Rating1], //0
[date2, User2, Rating2], //1
[date3, User3, Rating3] //2
Second Array example:
// 0 , 1 , 2
[date1, User1, Rating1]
I'd like to be able to acces my data like this:
MATCH (n) RETURN n.reviews[2] (output: date3, User3, Rating3)
MATCH (n) RETURN n.reviews[2][0] (output: date3)
MATCH (n) RETURN n.reviews[1][1] (output: User2)
Is there any way to do this?
Using APOC Procedures, you can use the apoc.convert.fromJsonList() function to convert the list, though you'll need to make sure each of the subitems in the arrays is quoted so they'll be interpreted as strings.
WITH "[['date1', 'User1', 'Rating1'], ['date2', 'User2', 'Rating2'], ['date3', 'User3', 'Rating3']]" as inputString
WITH apoc.convert.fromJsonList(input) as input
RETURN input[2][0] //returns 'date3'
Just a note, the conversion functions are currently missing in the APOC docs, but you can reference them and their signature by entering this in the Neo4j browser:
CALL apoc.help('fromjson')
And now for the bad news.
Although you can do this with literal input and parameters and convert from properties which are JSON strings, you cannot use a nested list as a property of a node or relationship, that's just a current limitation of the properties implementation.
That said, this feels like your modeling may need some improvement. We'd recommend using separate nodes for your reviews rather than this nested structure, so something like:
(:Product)-[:HAS_REVIEW]->(:Review)
Where the :Review node has the date and rating, and either has the user ID, or has a relationship to the user node who rated the product.
Usage would look something like:
MATCH (p:Product {id:12345})-[:HAS_REVIEW]->(r)
WITH p, collect(r) as reviews
...
At that point you have an (unordered) array of the review nodes, and can do index access to get a review at a particular index, then use dot notation to access the property or properties you want. If you want ordering, you'll need to do an explicit ORDER BY before the collect(), and you'll need something to order it by, probably the date of the review.

Any way to whitelist or blacklist properties returned from a node?

Nodes returned in neo4j seem to be special, in that they output as JSON objects, and they don't appear at all if they're null.
An example:
I have a :Person object, and they can have 0 or more :Friend relationships to another :Person.
Let's say that a :Person has the following properties: ID, firstName, lastName, sensitiveThing.
sensitiveThing is a property that might be used by our system, or could be personally accessible to the user themselves, but we don't want to return it to any other user.
If I want a query to give me back data of my friends, and friends of those friends, I might use a query like this
MATCH (me:User{ID:777})-[:Friend]-(friend:User)
WITH me, friend
OPTIONAL MATCH (friend)-[:Friend]-(foaf:User)
WHERE me <> foaf
RETURN friend.ID, friend.firstName, friend.lastName, COLLECT(foaf) AS FriendOfAFriend
While this lets me nicely bundle up friends of friends as JSON objects within a JSON array, and correctly emits an empty array if a friend doesn't have any other friends besides me, I don't want to return sensitiveThing with this query.
If I try to replace COLLECT(foaf) with a custom object only including fields I care about, like this:
COLLECT({ID:(foaf.ID), firstName:(foaf.firstName), lastName:(foaf.lastName)})
then I get what I want...until I hit the case where there are no friends of friends. When I was working with nodes before, the object wouldn't even be emitted. But now, I would get something like this returned to me:
[{ID: (null), firstName: (null), lastName: (null)}]
This is obviously not what I want.
Ideally, I'm looking for a way to return a node as before, but whitelist or blacklist properties I want to emit, so I can retain the correct null handling if the node is null (from an optional match)
If I can't have that, then I'd like a way to use a custom object, but not return the object at all if all its fields are null.
Any other workarounds or tips for dealing with optional matches are more than welcome.
You can use apoc.map.removeKeys:
WITH {p1: 1, p2: 2, p3: 3, p4: 4} as node
CALL apoc.map.removeKeys( node, ['p2', 'p4'] ) YIELD value
RETURN value
I've never seen a way to whitelist or blacklist properties in the documentation.
However, you can return your custom object by chaining collect with extract:
MATCH (me:User{ID:777})-[:Friend]-(friend:User)
WITH me, friend
OPTIONAL MATCH (friend)-[:Friend]-(foaf:User)
WHERE me <> foaf
WITH friend, collect(foaf) AS FriendOfAFriend
RETURN friend.ID, friend.firstName, friend.lastName,
extract(foaf in FriendOfAFriend | {ID:(foaf.ID), firstName:(foaf.firstName), lastName:(foaf.lastName)}) AS FriendOfAFriend
collect will return an empty list if there are no friends, extract will keep it that way.

Cypher query with literal map syntax & dynamic keys

I'd like to make a cypher query that generates a specific json output. Part of this output includes an object with a dynamic amount of keys relative to the children of a parent node:
{
...
"parent_keystring" : {
child_node_one.name : child_node_one.foo
child_node_two.name : child_node_two.foo
child_node_three.name : child_node_three.foo
child_node_four.name : child_node_four.foo
child_node_five.name : child_node_five.foo
}
}
I've tried to create a cypher query but I do not believe I am close to achieving the desired output mentioned above:
MATCH (n)-[relone:SPECIFIC_RELATIONSHIP]->(child_node)
WHERE n.id='839930493049039430'
RETURN n.id AS id,
n.name AS name,
labels(n)[0] AS type,
{
COLLECT({
child.name : children.foo
}) AS rel_two_representation
} AS parent_keystring
I had planned for children.foo to be a count of how many occurrences of each particular relationship/child of the parent. Is there a way to make use of the reduce function? Where a report would generate based on analyzing the array proposed below? ie report would be a json object where each key is a distinct RELATIONSHIP and the property value would be the amount of times that relationship stems from the parent node?
Thank you greatly in advance for guidance you can offer.
I'm not sure that Cypher will let you use a variable to determine an object's key. Would using an Array work for you?
COLLECT([child.name, children.foo]) AS rel_two_representation
I think, Neo4j Server API output by itself should be considered as any database output (like MySQL). Even if it is possible to achieve, with default functionality, desired output - it is not natural way for database.
Probably you should look into creating your own server plugin. This allows you to implement any custom logic, with desired output.

Search text box in iOS is case sensitive

I have a text box in iOS in which a user can enter a name and retrieve the users with that name. I use the parse query below for this. Actually it works fine, but it is case sensitive.
For example when the user's name is "Martin" in parse table, and you enter "Martin" to text box, it finds it, but when you enter "martin" it doesn't find anything. Is there a way to use the "containString" not case sensitive?
var query = PFUser.query()
query!.whereKey("username", notEqualTo: PFUser.currentUser()!.username!)
query!.whereKey("profileName", containsString: self.userSearchField.text)
Edit:
I found another solution, I created a new column in my databse called "searchUser" and when a person signs up with a new user name a few versions of the user name are stored in that column (for example, all letters capital, all letters lowercase, only first letters lowercase). And use the "containsString" in that column. Now it has a higher possiblity to find that user. Something like below:
var varSearch1 = profileTxt.text
var varSearch2 = profileTxt.text.lowercaseString
var varSearch3 = profileTxt.text.uppercaseString
var varSearch4 = profileTxt.text.capitalizedString
user["searchUser"] = "\(varSearch1) \(varSearch2) \(varSearch3) \(varSearch4)"
As Hector Ramos (Parser) says in this post:
You'd need to query for the users and use localizedCaseInsensitiveCompare: locally. Using orderByAscending: will, as you have already found out, order the results alphabetically.
Because the case-insensitive query is very expensive. If you have few users, it's a feasible solution, but as one person points out in the discussion - loading 20k users is simply not great.
If you can use canonical field for it, it would surely make things easier for you.

Resources