Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
01-27-2020 10:02 AM
I need to assign values to a parameter in the path loop. Simplistically it's like this
MATCH p=(start:PORTS {N_ID: "180992417"})-[r*1..10]-(end:PORTS {N_ID: "12645867"})
with p, "1" as agg
foreach (rel In relationships(p) | SET agg=123)
RETURN * LIMIT 1
How do I do this? Thank you
01-27-2020 10:05 AM
I think this greatly depends on what you are trying to do. You haven't shown usage or reason for it. Please provide a comprehensive example illustrating what your goal is and what you're trying to do to fulfill that goal.
01-27-2020 10:27 AM
Drew schematically. The relationships have a parameter and we go from the start node. Look at the parameter of the start node. In the example, it is 2. This means that the path must be selected further using this initial parameter, i.e. = 2. The rest of the path is not valid. How do I make it not try to go over links that have a parameter that is not equal to the initial parameter (that is, 2)
In this example the only correct path is port1 -> port2 -> port4
01-27-2020 10:31 AM
Thanks, that helps, though I still have a few questions:
Is the parameter that should restrict the path provided to the query? Or is it something that is encountered dynamically (like the first relationship in the path)? Can the param value change in the path, and how should that affect the expansion?
01-27-2020 10:47 AM
Good question. Yes, the number changes along the way, which is also a problem. If there is a different number at the exit, then we go further with a new number. Moreover, the number may not be (NULL), then we go further with the previous number. In the new example, the correct path is
port1 {param = 2} -> port2 {param = NULL it is -> 2} -> port4 {param=2} -> port5 {ATTENTION! param = 1} -> port7 {param = 1}
01-27-2020 10:53 AM
So if I understand the logic here, starting from port 1, we traverse a relationship where param=2, so from port2 we can only traverse a relationship where param=2, or where param=NULL (meaning there is no param, since a null value is the same as nonexistence).
As far as going from port4 to port6, was that only possible because the previous param was NULL (otherwise it would have been restricted to whatever the param was at the previous relationship)?
01-27-2020 10:57 AM
When a parameter is changed (input parameter <> output parameter), there is only one output relationship from the node. This is how the system is designed. This is always the case 100%.
So I drew that there is only one link from port1. It can't be any other way.
The previous parameter can be either zero or = 2. I just drew a zero to show that we keep the value earlier, i.e. =2
01-27-2020 11:05 AM
So there seem to be different rules in effect depending on if there are multiple relationships from a node vs a single relationship from the node. You also seem to be using the same relationship types in either case.
As far as the jump from port2 to port4, is it correct that this was only possible because the param was not set on that relationship? And should the param be set to 2 in that case, or do you not want to write to the graph at that time?
I'm not so sure your use case is a good fit for Cypher, since there is some fairly complex logic at each step of the expansion that must consider other possible relationships and previous values of other relationships (plus the other possible relationships on a node restrict how to further traverse). You may want to think about creating your own custom procedure to evaluate if and how to expand, and then call that via Cypher to do your special expansion logic.
01-27-2020 11:12 AM
I thought about it. I was thinking of developing a function that returns a set_param value and calls it at every step in ALL.
In fact, we only need to remember one current parameter at each iteration step. If it changes, then forget the old one. So I want to somehow include the parameter in ALL, compare it with the current relationship, and if it changes, then forget the old value. Thus, in the process of searching for a path in ALL, I need only one parameter, but one that can be changed. Maybe this is an option through its function? Can I call my own functions in every step in ALL?
01-27-2020 11:34 AM
A function won't work for this, you need a procedure to do your conditional expansion. all() doesn't come into play here, since you won't be working with Cypher, you'll be using Java and the core API (the traversal API probably wouldn't be a good match).
The real problem I think is that you can only traverse relationships where the param is null only if there isn't another relationship to traverse that has the parameter to use. Cypher isn't great about comparing possible paths at each step of an expansion when you have complex conditions for a best fit that need to evaluate the alternate paths.
01-27-2020 11:50 AM
I did find a way to do this with Cypher, but it is ugly.
We're using several tricks here. We can't use FOREACH, we need to use UNWIND on indexes of the path, this also lets us use variables for the previous node which is needed so we can make sure (in the case where the current relationship's param is null) that there are no alternate relationships from the previous node that are a better fit.
Here's the test data I'm using:
unwind range(1,8) as id
create (p:Port {id:id, name:'port'+id});
match (p1:Port {id:1}),
(p2:Port {id:2}),
(p3:Port {id:3}),
(p4:Port {id:4}),
(p5:Port {id:5}),
(p6:Port {id:6}),
(p7:Port {id:7}),
(p8:Port {id:8})
create (p1)-[:r{param:2}]->(p2),
(p2)-[:r{param:1}]->(p5),
(p2)-[:r{param:3}]->(p3),
(p2)-[:r]->(p4),
(p4)-[:r{param:1}]->(p6),
(p6)-[:r{param:1}]->(p7),
(p6)-[:r{param:2}]->(p8)
and here's the query
MATCH (start:Port {name:'port1'})
MATCH path= (start)-[:r*]->(end)
WHERE not (end)-->() //not sure if you know your end node...in this case only considering nodes with no other outgoing rels
WITH path, end, [r in relationships(path) | r.param] as params
UNWIND range(1, length(path) - 1) as i
WITH path, params, nodes(path)[i] as prevNode, i
WITH path, params, size((prevNode)-[:r]->()) = 1 OR (params[i] IS NULL AND size([(prevNode)-[other:r]->() WHERE other.param = params[i-1] | other]) = 0) OR params[i] = params[i-1] as eval
WITH path, params, collect(eval) as pathEval
WHERE size(params) + pathEval + 1 and all(eval in pathEval where eval = true)
RETURN path, params
(the size(params) + pathEval + 1
is there because collect() ignores nulls...and if there is a null then something went wrong and the list will be smaller than expected and this size comparison will exclude that path)
You can of course change what is returned. The disadvantage of the Cypher approach vs a stored procedure is that this approach finds all possible paths and then filters, instead of filtering during expansion. In a case where there are many many paths this may not be an efficient approach.
All the sessions of the conference are now available online