SET in combination with CASE statement in cypher - neo4j

I am tryin to set two different relationship properties to a count, with a case construct depending on the value of another relationship property. There is a console at http://console.neo4j.org/?id=rt1ld5
the cnt column contains the number of times r.value occurs. The two first rows of the initial query in the console indicate that the term "Car" is linked to 1 document that is considered relevant, and to two documents that are considered not relevant.
I want to SET a property on the [:INTEREST] relation between (user) and (term) with two properties, indicating how many times an interest is linked to a document that is considered relevant or not. So for (John)-[r:INTEREST]->(Car) I want r.poscnt=1 and r.negcnt=2
I.m struggling with the CASE construct. I tried various ways, this was the closest I got.
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<- [r:RELEVANCE]-(u)
WITH int, t.name, r.value, count(*) AS cnt
CASE
WHEN r.value=1 THEN SET int.poscnt=cnt
WHEN r.value=-1 THEN SET int.negcnt=cnt
END
But it's returning an error
Error: Invalid input 'A': expected 'r/R' (line 3, column 2)
"CASE"
^

This did it! Also see console at http://console.neo4j.org/?id=rq2i7j
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<-[r:RELEVANCE]-(u)
WITH int, t,
SUM(CASE WHEN r.value= 1 THEN 1 ELSE 0 END ) AS poscnt,
SUM(CASE WHEN r.value= -1 THEN 1 ELSE 0 END ) AS negcnt
SET int.pos=poscnt,int.neg=negcnt
RETURN t.name,int.pos,int.neg

Is it important for you to keep positive and negative count separate? It seems you could have a score property summing positive and negative values.
MATCH (u:user)-[int:INTEREST]->()<-[:ISABOUT]-()<-[r:RELEVANCE]-(u)
SET int.score = SUM(r.value)
RETURN t.name, int.score
You already seem to have found a working solution but I'll add a note about CASE as I understand it. While CASE provides branching, I think it's correct to say that it is an expression and not a statement. It resembles a ternary operator more than a conditional statement.
As the expression
a > b ? x : y;
is resolved to a value, either x or y, that can be used in a statement, so also
CASE WHEN a > b THEN x ELSE y END
resolves to a value. You can then assign this value
result = CASE WHEN a > b THEN x ELSE y END
Your original query used the CASE expression like a conditional statement
CASE WHEN a > b THEN result = x ELSE result = y END
which resembles if-else
if a > b { result = x; } else { result = y; }
Someone may want to correct the terminology, the point is that in your working query you correctly let CASE resolve to a value to be used by SUM rather than put a conditional assignment inside CASE.

Related

Is there an way through we can fetch data from neo4j database based on different conditions

MATCH (a:Chemical{name:'abc'})-[r:On_Reacting_With]->(b:Chemical)
WHERE r.outputtime >'20'
RETURN count(b)
As in the above query I can get values where the outputtime is greater than 20. But I want to give the user a feature where he/she can fetch the data where outoutime can be greater, lesser or equal to a value. I want to know how can we pass the operator as params in code.
Aside: using string values for time comparison will not produce correct results unless all strings have the same length (including leading zero characters, as needed).
You can pass in an operator parameter and use a CASE clause. For instance:
MATCH (a:Chemical{name:'abc'})-[r:On_Reacting_With]->(b:Chemical)
WHERE
CASE $operator
WHEN '<' THEN r.outputtime < '20'
WHEN '>' THEN r.outputtime > '20'
ELSE r.outputtime = '20'
END
RETURN COUNT(b)

Execute multiple query based on multiple condition in cypher / apoc

In cypher or APOC, Is there a way to execute multiple query based on multiple condition.
I need something similar this APOC
CALL apoc.do.case([condition, query, condition, query, …​], elseQuery:'',
params:{}) yield value
But here as soon as we met 1st true condition it skip all further condition and query. I want to execute all those query where my condition is true.
In simple word , I am looking for something similar to java case statement (without break; between case)
Update
I ran following query to use multiple apoc.do.when but it seems only my second apoc.do.when is not executing:
CREATE (y:EVENT { _id: 1, localComponentID:'l1', externalComponentID:'e1'}) with y
call apoc.do.when(exists(y.localComponentID),"MATCH(e:EVENT) where
e.localComponentID = lcl and e._id <> y._id with y,e limit 1 create (y)-
[r:LOCAL_LINK]->(e)",'',{y:y,lcl:y.localComponentID}) YIELD value WITH value AS ignored, y
call apoc.do.when(exists(y.externalComponentID),"MATCH(e:EVENT) where
e.externalComponentID = ext and e._id <> y._id with y,e limit 1 create (y)-
[r:EXTERNAL_LINK]->(e)",'',{y:y, ext:y.externalComponentID}) YIELD value
WITH value AS ignored return ignored
If I run above query two time with _id = 1 in first run and _id=2 in second run, I expect two EVENT connected with LOCAL_LINK and EXTERNAL_LINK. But I am only getting LOCAL_LINK between them not the EXTERNAL_LINK. I am not sure what I am doing wrong.
Note : I am using limit 1 because In case of multiple match I just want to create LINK with one node.
Update 2
Got it working , In my sample query I was that not returning y from first apoc.do.when
Here is the updated query which works:
CREATE (y:EVENT { _id: 1, localComponentID:'l1', externalComponentID:'e1'}) with y
call apoc.do.when(exists(y.localComponentID),"MATCH(e:EVENT) where
e.localComponentID = lcl and e._id <> y._id with y,e limit 1
create (y)-[r:LOCAL_LINK]->(e) RETURN y",'',
{y:y,lcl:y.localComponentID}) YIELD value WITH value AS ignored, y
call apoc.do.when(exists(y.externalComponentID),"MATCH(e:EVENT) where
e.externalComponentID = ext and e._id <> y._id with y,e limit 1
create (y)-[r:EXTERNAL_LINK]->(e)",'',{y:y, ext:y.externalComponentID})
YIELD value
WITH value AS ignored return ignored
You can just call the APOC function apoc.do.when for each condition/query pair (with an empty string as the else argument).
For example:
CALL apoc.do.when(<condition1>, <query1>, '', {}) YIELD value
WITH value AS ignored
CALL apoc.do.when(<condition2>, <query2>, '', {}) YIELD value
WITH value AS ignored
.
.
.
Since your comments indicate your queries are all write-only, the above example assigns the return values to an ignored variable (that you can ignore).

Finding the consecutive win in Cypher query language

from fig we can see that Arsenal have won three match consecutively but I could not write the query.
Here is a query that should return the maximum number of consecutive wins for Arsenal:
MATCH (a:Club {name:'Arsenal FC'})-[r:played_with]-(:Club)
WITH ((CASE a.name WHEN r.home THEN 1 ELSE -1 END) * (TOINT(r.score[0]) - TOINT(r.score[1]))) > 0 AS win, r
ORDER BY TOINT(r.time)
RETURN REDUCE(s = {max: 0, curr: 0}, w IN COLLECT(win) |
CASE WHEN w
THEN {
max: CASE WHEN s.max < s.curr + 1 THEN s.curr + 1 ELSE s.max END,
curr: s.curr + 1}
ELSE {max: s.max, curr: 0}
END
).max AS result;
The WITH clause sets the win variable to true iff Arsenal won a particular game. Notice that the ORDER BY clause converts the time property to an integer, because the ordering of numeric strings does not work properly if the strings could be of different lengths (I am being a bit picky here, admittedly). The REDUCE function is used to calculate the maximum number of consecutive wins.
======
Finally, here are some suggestions for some improvements to your data model. For example:
It looks like your played_with relationship always points from the home team to the away team. If so, you can get rid of the redundant home and away properties, and you can also rename the relationship type to HOSTED to make the direction of the relationship more clear.
The scores and time should be stored as integers, not strings. That would make your queries more efficient, and easier to write and understand.
You could also consider splitting the scores property into two scalar properties, say homeScore and awayScore, which would make your code more clear. There seems to be no advantage to storing the scores in an array.
If you made all the above suggested changes, then you would just need to change the beginning of the above query to this:
MATCH (a:Club {name:'Arsenal FC'})-[r:HOSTED]-(:Club)
WITH ((CASE a WHEN STARTNODE(r) THEN 1 ELSE -1 END) * (r.homeScore - r.awayScore)) > 0 AS win, r
ORDER BY r.time
...

FILTER by two condition then SUM the results

I not sure if I understand how FILTER is working.
I would like to SUM only the results that satisfy both conditions in the FILTER and get 8+10=18, but it seems I'm getting 8+9+10=27 as if the first condition is ignored.
Both =SUM(FILTER(E1:E10,MATCH(D1:D10, G1:G4), E1:E10 > 7)) and =SUM(FILTER(E1:E10,MATCH(D1:D10, G1:G4) * (E1:E10 > 7))) return 27
Have any ideas?
Here is an example and a screenshot
The crucial thing is putting the third argument into the Match function to specify an exact match
=SUM(FILTER(E1:E10,MATCH(D1:D10, G1:G4,0), E1:E10 > 7))
Otherwise you get the position of the largest value less than or equal to the lookup value: e.g. for "G" you would get a match with "D" which would return 4. So the Match function in your original formula always returns a number >=1 which is treated as True.
It's interesting that the above formula works actually because a non-match will return #N/A but apparently it's treated as false - I don't know if this is documented.
I would always put
=SUM(FILTER(E1:E10,isnumber(MATCH(D1:D10, G1:G4,0)), E1:E10 > 7))
to make it clearer.

ternary operator/default value in neo4j cypher

I need to implement something of a ternary operator that can help me return some default values from cypher query itself.
Scenario is -
if an employee's city is Delhi, return 5 else return 10
Something like a ternary operator.
start employee = node(5)
return employee.city == 'DELHI' ? 5 : 10 as val;
I tried things like
start employee = node(5)
return coalesce (employee.city == 'DELHI', 5)
but no luck.
Is there a way to implement such a scenario in neo4j be it Cypher or Traversal.
Unfortunately it is not supported out of the box but here is a hack to do it, using filter, head and collection literals.
The idea is to have a two element list and a filter expression that becomes true for the first element for your "true-branch" and alternatively true for the second element in the list whic represents the value of your false-branch.
see this console example: http://console.neo4j.org/r/6tig7g
start n=node(5) return head(filter( a in [5,10] : n.city = 'DELHI' OR a = 10))
so generally:
head(filter( a in [true-result,false-result] : CONDITION OR a = false-result))
I know this is a really old question, but since Google landed me here and I have an answer (Cypher in Neo4J 3.5+):
MATCH (employee:Employee)
RETURN
CASE employee.city WHEN "DELHI" THEN 5 ELSE 10 END AS val

Resources