0
votes

I use neo4j to represent unique flows in my system.
I get data represented in JSON and before writing it to neo4j I need to make sure it does not exist yet.
We can assume there is only one relation between 2 nodes and that there's only 1 entry point and 1-way relations.

To check if the graph exists I need to match the nodes, the relations, and the relation count.
I'm currently struggling with the count (without it I'm only checking if a graph is contained).
Here's an example of a generated query that does not work (return empty result):

MATCH
(n:User {userId: 1234}),
(n_0:User {userId: 3345}),
(n_1:Group {groupId: 8765}),
(n_1_0:Event {eventId:3456})

OPTIONAL MATCH
(n)-->(n_rel),
(n_0)-->(n_0_rel),
(n_1)-->(n_1_rel),
(n_1_0)-->(n_1_0_rel)

WITH
n, count(n_rel) AS n_count,
n_0, count(n_0_rel) AS n_0_count,
n_1, count(n_1_rel) AS n_1_count,
n_1_0, count(n_1_0_rel) AS n_1_0_count

WHERE (n)-[:PING {someProp:true}]->(n_0)
AND   (n)-[:JOIN {someProp:"cool"}]->(n_1)
AND   (n_1)-[:PUBLISH {otherProp: "Hello"}]->(n_1_0)
AND   n_count = 2
AND   n_0_count = 0
AND   n_1_count = 1
AND   n_1_0_count = 0

RETURN n

Here's a working one without count checks:

MATCH
(n:User {userId: 1234}),
(n_0:User {userId: 3345}),
(n_1:Group {groupId: 8765}),
(n_1_0:Event {eventId:3456})

WHERE (n)-[:PING {someProp:true}]->(n_0)
AND   (n)-[:JOIN {someProp:"cool"}]->(n_1)
AND   (n_1)-[:PUBLISH {otherProp: "Hello"}]->(n_1_0)

RETURN n

What am I missing when trying to get the relations count?

1

1 Answers

1
votes

Edit: Jump to the last Cypher statement below for the final answer. Otherwise, feel free to read thru the saga of discovery

I think the issue is that in the first query the entire WHERE clause is only being applied to the WITH. In the Cypher below I broke the WHERE clause from the first query into two WHEREs, one for the MATCH and one for the WITH. Hopefully this will give the expected results.

MATCH
(n:User {userId: 1234}),
(n_0:User {userId: 3345}),
(n_1:Group {groupId: 8765}),
(n_1_0:Event {eventId:3456})

WHERE (n)-[:PING {someProp:true}]->(n_0)
AND   (n)-[:JOIN {someProp:"cool"}]->(n_1)
AND   (n_1)-[:PUBLISH {otherProp: "Hello"}]->(n_1_0)

WITH n, n_0, n_1, n_1_0

OPTIONAL MATCH
(n)-->(n_rel),
(n_0)-->(n_0_rel),
(n_1)-->(n_1_rel),
(n_1_0)-->(n_1_0_rel)

WITH
n, count(n_rel) AS n_count,
n_0, count(n_0_rel) AS n_0_count,
n_1, count(n_1_rel) AS n_1_count,
n_1_0, count(n_1_0_rel) AS n_1_0_count

WHERE
n_count = 2
AND   n_0_count = 0
AND   n_1_count = 1
AND   n_1_0_count = 0

RETURN n

Using the Movies graph I came up with this query. I believe it's roughly equivalent to your query and follows the same pattern I originally suggested. It has the same issue you found. When the OPTIONAL MATCH doesn't find any results then nothing is returned.

MATCH
  (jamest:Person {name: "James Thompson"}),
  (jessicat:Person {name: "Jessica Thompson"})
WHERE 
  (jamest)-[:FOLLOWS]->(jessicat)

WITH jamest, jessicat

OPTIONAL MATCH
  (jamest)-[:REVIEWED]->(jamest_rev_rel), // James Thompson has 2 REVIEWED relationships
  (jessicat)-[:FOLLOWS]->(jessicat_fol_rel) // Jessica Thompson has no outbound FOLLOWS relationships
WITH
  jamest, count(jamest_rev_rel) AS jamest_rev_rel_count,
  jessicat, count(jessicat_fol_rel) AS jessicat_fol_rel_count
WHERE
  jamest_rev_rel_count = 2
  AND jessicat_fol_rel_count = 0

RETURN jamest, jessicat // No results returned

I reworked the query into this. This one returns the expected results. It feels overly cumbersome but hopefully it'll give you something to work with. I'll keep tinkering with it.

MATCH
  (jamest:Person {name: "James Thompson"}),
  (jessicat:Person {name: "Jessica Thompson"})
WHERE 
  (jamest)-[:FOLLOWS]->(jessicat)

WITH jamest, jessicat

OPTIONAL MATCH
  (jamest)-[:REVIEWED]->(jamest_rev_rel) 
WITH
  jessicat, jamest, count(jamest_rev_rel) as jamest_rev_rel_count
WHERE 
  jamest_rev_rel_count = 2 // James Thompson has 2 REVIEWED relationships

WITH jamest, jessicat

OPTIONAL MATCH
  (jessicat)-[:FOLLOWS]->(jessicat_fol_rel) 
WITH
  jamest, jessicat, count(jessicat_fol_rel) AS jessicat_fol_rel_count
WHERE 
  jessicat_fol_rel_count = 0 // Jessica Thompson has no outbound FOLLOWS relationships

RETURN jamest, jessicat // The two nodes are returned as expected

The root of the issue turned out to be having one OPTIONAL MATCH with multiple, comma separated patterns versus multiple OPTIONAL MATCH statements. In the former, all of the separate patterns are considered to be a single pattern. Whereas in the latter they're distinct which is what this query needs. This SO question gives more details.

The query can be leaned down quite a bit though. This version gives the same results as the above and is a lot more readable in my opinion

MATCH
  (jamest:Person {name: "James Thompson"}),
  (jessicat:Person {name: "Jessica Thompson"})
OPTIONAL MATCH 
  (jamest)-[:REVIEWED]->(jamest_rev_rel)
OPTIONAL MATCH 
  (jessicat)-[:FOLLOWS]->(jessicat_fol_rel) 
WITH
  jessicat, jamest, 
  count(jamest_rev_rel) as jamest_rev_rel_count, 
  count(jessicat_fol_rel) as jessicat_fol_rel_count
WHERE 
  jamest_rev_rel_count = 2 // James Thompson has 2 outbount REVIEWED relationships 
  AND jessicat_fol_rel_count = 0 // Jessica Thompson has 0 outbound FOLLOWS relationships

RETURN 
  jamest, jessicat // The two nodes are returned as expected