Neo4J Arrays in MATCH query - ruby-on-rails

The intention of my Query is to mark similar words.
CREATE CONSTRAINT ON (n:Word) ASSERT n.title IS UNIQUE
MATCH (n) WHERE ID(n)={id}
MERGE (o:Word{title:{title}})
WITH n,o MERGE n-[r:SIMILAR{location:'{location}'}]->o
RETURN ID(o)
n is a existing Word. I want to create the relationsship & the other Word (o) if they don't exist yet.
The Problem with this query is, that it works for one title, but if I use a Array with titles the title of the Word o is the whole Array.
Can you suggest me another Query that does the same and/or a way to pass multiple values to title.
I'm using the Neography Gem on Rails e.g. the REST API

To use individual values in a parameter array you can use FOREACH, something like
MATCH (n)
WHERE ID (n) = {id}
FOREACH (t IN {title} |
MERGE (o:Word {title:t})
MERGE n-[:SIMILAR]->o
)
If you want to pass location also as a parameter (it is actually a string literal in your current query), such that merge operations for n should happen for each title, location pair in a parameter array, you can try
FOREACH (map IN {maps} |
MERGE (o:Word {title:map.title})
MERGE n-[:SIMILAR {location:map.location}]->o
)
with a parameter that looks something like
{
"maps": [
{
"title":"neography",
"location":"1.."
},{
"title":"coreography",
"location":"3.."
}
]
}
Other suggestions:
It's usually not great to look up nodes by internal id from parameter. In some cases when chaining queries it may be fine, but in most cases label index lookup would be better: MATCH (n:Word {title:"geography"})
If you are not using the transactional cypher endpoint, give it a shot. You can then make one or more calls with one or more queries in each call within one transaction. Performance improves and you may find you don't need to send the more complex parameter object, but can send many simple queries.

Related

Can we make cypher field query Case-Insensitive

It may seem duplicate of this but it is not. Can we make cypher query case insensitive based on fields. I know we can use regex for values but we need it based on fields.
e.g.
MATCH (c:customer) WHERE c.CUSTOMERNUMBER = '1088' RETURN c
Above query returns a result, but following does not
MATCH (c:Customer) WHERE c.CustomerNumber = '1088' RETURN c
Here lable Customer and property CustomerNumber are having different cases.
You can use PROPERTIES to get a map representation of a node, and then use KEYS so that you can iterate over them. Because "Name", "NAME", and "Prop1" are all equally unique property names, and they can all or none exist, as far as the DB is concerned. You will have to iterate every property of the node to find a field that matches your criteria.
MATCH (n)
WHERE ANY(key in KEYS(n) WHERE lower(key)="name" AND n[key]="Neo")
RETURN n
This is more flexible than simple case insensitivity, but it is also expensive.

Create node and relationship given parent node

I am creating a word tree but when I execute this cypher query:
word = "MATCH {} MERGE {}-[:contains]->(w:WORD {{name:'{}'}}) RETURN w"
.format(parent_node, parent_node, locality[i])
where parent_node has a type Node
It throws this error:
py2neo.cypher.error.statement.InvalidSyntax: Can't create `n8823` with properties or labels here. It already exists in this context
formatted query looks like this:
'MATCH (n8823:HEAD {name:"sanjay"}) MERGE (n8823:HEAD {name:"sanjay"})-[:contains]->(w:WORD {name:\'colony\'}) RETURN w'
The formatted query is broken and won't work, but I also don't see how that could be what the formatted query actually looks like. When you do your string format you pass the same parameter (parent_node) twice so the final string should repeat whatever that parameter looks like. It doesn't, and instead has two different patterns for the match and merge clauses.
Your query should look something like
MATCH (n8823:Head {name: "sanjay"})
MERGE (n8823)-[:CONTAINS]->(w:Word {name: "colony"})
RETURN w
It's probably a bad idea to do string formatting on a Node object. Better to either use property values from your node object in a Cypher query to match the right node (and only the variable that you bind the matched node to in the merge clause) or use the methods of the node object to do the merge.
Although the MERGE clause is able to bind identifiers (like n8823), Cypher unfortunately does not allow MERGE to re-bind an identifier that had already been bound -- even if it would not actually change the binding. (On the other hand, the MATCH clause does allow "rebinding" to the same binding.) Simply re-using a bound identifier is OK, though.
So, the workaround is to change your Cypher query to re-use the bound identifier. Also, the recommended way to dynamically specify query data without changing the overall structure of a query is to use "query parameters". For py2neo, code along these lines should work for you (note that the parent_name variable would contain a name string, like "sanjay"):
from py2neo import Graph
graph = Graph()
cypher = graph.cypher
results = cypher.execute(
"MATCH (foo:{name:{a}}) MERGE (foo)-[:contains]->(w:WORD {{name:'{b}'}}) RETURN w",
a=parent_name, b=locality[i])

Neo4j: Cypher query on property array

I have a domain class which has a property by name "alias" which is an arraylist of strings like below:
private List<String> alias;
alias contains the following values: {"John Doe","Alex Smith","Greg Walsh"}
I'd like to be able to make a query like: "I saw Smith today" using my repository query shown below and get the array value Output "Alex Smith":
#Query("MATCH (p:Person) WHERE {0} IN p.alias RETURN p")
Iterable<Person> findByAlias(String query);
I tried a bunch of different queries like the one shown above but this would only match if the input query matches the array value exactly.
I want do a input query sub-string match with the array values.
Eg:
Input Query: "I saw Smith today"
Output: "Alex Smith"
Summary
It is sort of possible to do what you want, but the query will be horribly ugly and slow. You'd be better off using nodes and relationships instead of collection properties: it will make your queries more sensible and allows you to use a full-text index. You should also figure out what part of the 'input string' you are looking for before you send your query to the database. As it stands, you are confusing the regex pattern with the data it is supposed to match and even if it were possible to express your intention as a regex it would be much better to do that processing your application, before sending the query.
1) WHERE ... IN ... doesn't do regex
WHERE x IN y will not treat x as a regular expression, it will take x's value for what it is and look for an exact match. WHERE ... IN ... is analogous to WHERE ... = ... in this sense, and you would need the analogue of =~ for collections, something like IN~, for this. There is no such construct in Cypher.
2) You can do regex over collections with predicates, but it is inefficient
You can use a string as a regular expression to test for matches over a collection by using a predicate like ANY or FILTER.
CREATE (p:Person {collectionProperty:["Paulo","Jean-Paul"]})
and
WITH "(?i).*Paul" as param
MATCH (p:Person)
WHERE ANY(item IN p.collectionProperty WHERE item =~ param)
RETURN p
will return the node because it makes a successful regular expression match on "Jean-Paul".
This, however, will have terrible performance since you will run your regular expression on every item in every collectionProperty for every :Person in your database. The solution is to use a full-text index, but his query can't use indices for two reasons:
the values you are querying are in an array
you are using regular expressions to filter results instead of doing an index query
3) You can't do regex over collections at all with your kind of input
The biggest problem with your query is that you are trying to turn "I saw Smith today" into "Smith" by adding some regular expression sugar. How do you intend to do that? If you use the string as a regular expression, each of those characters is a literal character expected to be in the data that you match it on. You are confused about .*, which when used in 'Smith.*' would match 'Smith' plus zero or more additional characters in the data. But you try to use it to say that zero or more characters may follow something in the pattern.
Take the query in your comment:
MATCH (p:Person)
WHERE '(?i).*I saw Smith today.*' IN p.alias
RETURN p
The regular expression '(?i).*I saw Smith today.*' will match
ignoring the case of the literal string–'i SAW smith TOday', etc.
with zero or more characters before and after the literal string–'Yes, I saw Smith today and he looked happy.'
But adding .* won't somehow magically make the pattern mean '.*Smith.*'. What's more, it's almost impossible to express 'I saw Smith today' as a subset of 'Alex Smith' by any amount of added regular expression sugar. Instead, you should process that string and figure out what parts of it you want to use in a regular expression before you send your query. How is the database to know that 'Smith' is the part of the input string that you want to use? However it is that you know it, you should know it before you send the query, and only include that relevant part.
Aside: examples of added regular expression sugar that won't work and why
You could insert a ? after each character in the pattern to make each character optional
RETURN "Smith" =~ "I? ?s?a?w? ?S?m?i?t?h? ?t?o?d?a?y?"
But now your pattern is way too loosie goosie, and it will match strings like 'I sat today' and 'sam toy'. Moreover, it won't match 'Alex Smith' unless you prepend .*, but then it is even more loosie goosie and will match any string whatever.
You could divide characters that belong together into groups and make the groups and the spaces between them optional.
RETURN "Smith" =~ "(I)? ?(saw)? ?(Smith)? ?(today)?"
But this also is too broad, fails to match 'Alex Smith' and will match any string whatever if you prepend .*.
4) Bad solution
The only 'solution' I can think of is a hideous query that splits your string on whitespace, concatenates some regex sugar into each word and compares it as a regular expression in a predicate clause. It really is hideous, and it assumes that you already know that you want match the words in the string and not the whole string, in which case you should be doing that processing before you send your query and not in Cypher. Look upon this hideousness and weep
WITH "I saw Paul today" AS paramString
MATCH (p:Person)
WHERE ANY (param IN split(paramString, ' ')
WHERE ANY (item IN p.collectionProperty
WHERE item =~('(?i).*' + param)))
RETURN p
5) Conclusion
The conclusion is as follows:
1) Change your model.
Keep a node for each alias like so
CREATE (a:Alias)
SET a.aliasId = "Alex Smith"
Create a full-text index for these nodes. See blog and docs for the generic case and docs for SDN.
Connect the nodes that now have the alias in a collection property to the new alias node with a relationship.
Look up the alias node that you want and follow the relationship to the node that 'has' the alias. A node can still have many aliases, but you no longer store them in a collection property–your query logic will be more straightforward and you will benefit from the full-text lucene index. Query with START n=node:indexName("query") when using cypher and use findAllByQuery() in SDN. This is necessary for the query to use the full-text index.
Your query might then finally look something like
START n=node:myIndex("aliasId:*smith")
MATCH n<-[:HAS_ALIAS]-smith
RETURN smith
2) Don't do all your work in the database.
If your program is supposed to receive a string like 'I saw Smith today' and give back a node based on a pattern match on 'Smith', then don't send 'I saw' and 'today' to the database. You're better off identifying 'Smith' as the relevant part of the string in your application–when you send the query you should already know what it is you want.
You should use something like this:
MATCH (n:Test)
WHERE single(x IN n.prop WHERE x = "elem1")
RETURN n
It checks that collection has exactly one "elem1".
More info.

Keep track of Changes in Neo4j - Achieve functionality like a "flag" variable in standard programming

Initial setup of the sample database is provided link to console
There are various cases and within each case, there are performers(with properties id and name). This is the continuation of problems defined problem statement and solution to unique node creation
The solution in the second link is (credits to Christophe Willemsen
)
MATCH (n:Performer)
WITH collect(DISTINCT (n.name)) AS names
UNWIND names as name
MERGE (nn:NewUniqueNode {name:name})
WITH names
MATCH (c:Case)
MATCH (p1)-[r:RELATES_TO]->(p2)<-[:RELATES]-(c)-[:RELATES]->(p1)
WITH r
ORDER BY r.length
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
SET rf.strength = CASE WHEN rf.strength IS NULL THEN r.value ELSE rf.strength + r.value END
This solution achieved what was asked for.
But I need to achieve something like this.
foreach (Case.id in the database)
{
foreach(distinct value of r.Length)
{
//update value property of node normal
normal.value=normal.value+0.5^(r.Length-2)
//create new nodes and add the result as their relationship or merge it to existing one
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
//
rf.strength=rf.strength + r.value*0.5^(r.Length-2);
}
}
The problem is to track the change in the case and then the r.Length property. How can it be achieved in Cypher?
I will not redo the last part, where setting strengths.
One thing though, in your console link, there is only one Normal node, so why do you need to iterate over each case, you can just match distinct relationships lengths.
By the way for the first part :
MATCH (n:Case)
MATCH (n)-[:RELATES]->()-[r:RELATES_TO]->()<-[:RELATES]-(n)
WITH collect(DISTINCT (r.length)) AS lengths
MATCH (normal:Normal)
UNWIND lengths AS l
SET normal.value = normal.value +(0.5^l)
RETURN normal.value
Explanations :
Match the cases
Foreach case, match the RELATES_TO relationships of Performers for that Case
collect distinct lengths
Match the Normal node, iterate the the distinct lengths collection and setting the proper value on the normal node

Neo4j rename property using regex of current property value

From my research (lots of Googling), I can not see that this is possible, but it is still worth asking I think. I have a large collection of nodes like:
(org:Organization {name: "Organization 1234"})
where 1234 can be any non-negative integer.
In order to update the DB to work with a new API, I am wanting to rename each of these in the collection so that the result would look something like:
(org:Organization {name: "Org_1234"})
So, I need to mashup Org_ with the [0-9]+ regex match on the current property.
Truly, I am at a loss on where to even start. All I see in the documentation is that regex can be used as a predicate in the WHERE clause (WHERE n.property =~ {regex}). Is there a way using just Cypher as I am not using a Java library?
Assuming there is always a single space between "Organization" and the integer, you can brute force this pretty easily with just string functions.
CREATE (:Organization {name:'Organization 1234'}),
(:Organization {name:'Organization 5678'})
MATCH (o:Organization)
WITH o, SPLIT(o.name, " ")[1] AS id
SET o.name = "Org_" + id
RETURN o.name
Which returns
o.name
Org_1234
Org_5678

Resources