Toggle relationship in Neo4J - neo4j

I'm trying to implement follow/unfollow in Neo4J. I would like to write a query would toggle the relationship between two nodes.
I currently have the following query:
neoSession.writeTransaction(tx => tx.run('MATCH (me:User), (other:User) WHERE ID(me) = $me AND ID(other) = $other OPTIONAL MATCH (me)-[af:FOLLOWS]->(other) CALL apoc.do.when(af IS NULL, CREATE (me)-[f:FOLLOWS]->(other), DELETE af)', { me: req.user_id, other: req.body.user, datetime: Date.now() }));
Prettified query-only:
MATCH (me:User), (other:User)
WHERE ID(me) = $me AND ID(other) = $other
OPTIONAL MATCH (me)-[af:FOLLOWS]->(other)
CALL
apoc.do.when(
af IS NULL,
CREATE (me)-[f:FOLLOWS]->(other),
DELETE af
)
But this results in the error
Neo4jError: Invalid input '>' (line 1, column 169 (offset: 168))
"MATCH (me:User), (other:User) WHERE ID(me) = $me AND ID(other) = $other OPTIONAL MATCH (me)-[af:FOLLOWS]->(other) CALL apoc.do.when(af IS NULL, CREATE (me)-[f:FOLLOWS]->(other), DELETE af)"

The queries (last two arguments) to apoc.do.when() have to be strings, so quote each of them.
Also, in order for each of those queries to use those variables, you need to pass those variables in a parameter map as a 4th argument.
Each of the conditional queries must RETURN something, otherwise there will be no rows yielded and anything after would be a no-op.
The call must YIELD value, so that needs to be present, and last, a query cannot end with a procedure call, so you need to RETURN something.
This one should work, you can adjust it as needed:
MATCH (me:User), (other:User)
WHERE ID(me) = $me AND ID(other) = $other
OPTIONAL MATCH (me)-[af:FOLLOWS]->(other)
CALL
apoc.do.when(
af IS NULL,
"CREATE (me)-[f:FOLLOWS]->(other) RETURN f",
"DELETE af RETURN null as f",
{me:me, af:af}
) YIELD value
RETURN value.f as f

Related

How I need to use these CALLs correctly?

I've used first CALL after LOAD CSV for splitting transactions and second for creating relations between two types of nodes - Legal_Entity and Natural_Person. SYSTEM node is my analog for dicts contains associations between numbers - numeric interpretations of relations - and text of these relations.
In relation to is relation type for Legal_Entity or for Natural_Person I need to connect others nodes (a.e., if code is 100 it means LEGAL-LEGAL connection with text "has a division". If code is 110 it means LEGAL-PHYSICAL connection with text "founded by").
Now I need to make a request that would determine which type of node the connection is being built to and build it accordingly.
If it's needed I can add more clarify info for used data.
:auto LOAD CSV WITH HEADERS FROM 'file:///CSVs/связи_фикс_все.csv' as row
call {
with row
match (legal:Legal_Entity {hid_party: row['first_related_hid']})
match (sys:SYSTEM)
with sys, legal,
row,
sys['type_' + tostring(row['id_relation_type'])] as relation_data,
sys[row['second_related_type']] as rel_type
CALL {
WITH rel_type, relation_data, row, legal
WITH rel_type, relation_data, row, legal
WHERE rel_type = 'Legal_Entity'
match (legal_2:Legal_Entity {hid_party : row['second_related_hid']})
CALL apoc.create.relationship(legal, relation_data[1], NULL, legal_2) YIELD rel1
CALL apoc.create.relationship(legal_2, relation_data[3], NULL, legal) YIELD rel2
return rel_type as rel_type_2
UNION
WITH rel_type, relation_data, row, legal
WITH rel_type, relation_data, row, legal
WHERE rel_type = 'Natural_Person'
match (natural:Legal_Entity {hid_party : row['second_related_hid']})
CALL apoc.create.relationship(legal, relation_data[1], NULL, natural) YIELD rel1
CALL apoc.create.relationship(natural, relation_data[3], NULL, legal) YIELD rel2
return rel_type as rel_type_2
} return rel_type_2
} IN TRANSACTIONS of 10 rows
The subqueries seem to be behaving weird when you have them nested. You can simply use PERIODIC COMMIT if you want to import data in batches and get rid of the top-level subquery.
:auto USING PERIODIC COMMIT 10
LOAD CSV WITH HEADERS FROM 'file:///CSVs/связи_фикс_все.csv' as row
with row
MERGE (legal:Legal_Entity {hid_party: row['first_related_hid']})
MERGE (sys:SYSTEM)
with sys, legal,
row,
sys['type_' + tostring(row['id_relation_type'])] as relation_data,
sys[row['second_related_type']] as rel_type
CALL {
WITH rel_type, relation_data, row, legal
WITH rel_type, relation_data, row, legal
WHERE rel_type = 'Legal_Entity'
match (legal_2:Legal_Entity {hid_party : row['second_related_hid']})
CALL apoc.create.relationship(legal, relation_data[1], NULL, legal_2) YIELD rel AS rel1
CALL apoc.create.relationship(legal_2, relation_data[3], NULL, legal) YIELD rel AS rel2
return rel_type as rel_type_2
UNION
WITH rel_type, relation_data, row, legal
WITH rel_type, relation_data, row, legal
WHERE rel_type = 'Natural_Person'
match (natural:Legal_Entity {hid_party : row['second_related_hid']})
CALL apoc.create.relationship(legal, relation_data[1], NULL, natural) YIELD rel AS rel1
CALL apoc.create.relationship(natural, relation_data[3], NULL, legal) YIELD rel AS rel2
return rel_type as rel_type_2
}
RETURN distinct 'done'

How to continue executing a Neo4J Cypher request after MATCH is not matched?

I have the following params in my Neo4J:
{
"lists": [
{
"from": "someone",
"to": "somebody"
}
]
}
And the following query:
MATCH (c:Concept{name:'infranodus'}) WITH c, $lists AS list
UNWIND CASE WHEN list = [{}] THEN [null] ELSE list END AS l
WITH l
MATCH (cp1:Concept{name:l.from})
WITH cp1
MATCH (cp2:Concept{name:'somebody'})
RETURN cp1,cp2;
The query above will work.
However, if I replace l.from with a non-existent parameter, e.g. l.about, then — as the match doesn't happen — the second cp2 match doesn't fire.
How can I change this behavior and continue executing this query even if cp1 is not found? Maybe there's a way to pass on a dummy variable as a result?
MATCH (c:Concept{name:'infranodus'}) WITH c, $lists AS list
UNWIND CASE WHEN list = [{}] THEN [null] ELSE list END AS l
WITH l
MATCH (cp1:Concept{name:l.about})
WITH cp1
MATCH (cp2:Concept{name:'somebody'})
RETURN cp1,cp2;
Use OPTIONAL MATCH. If there is no match is found, then it will use NULL for the missing part of the pattern. It is similar to outer join in SQL.
NEW:
OPTIONAL MATCH (cp1:Concept{name:l.about})
OLD:
MATCH (cp1:Concept{name:l.about})
You can maybe replace it with an IN predicate ?
For eg :
WITH {from: 'Matt Olg', about: 'Matthew Olg'}
AS l
MATCH (n:Person)
WHERE n.name IN [l.from, l.to]
RETURN n.name
╒══════════╕
│"n.name" │
╞══════════╡
│"Matt Olg"│
└──────────┘

How to execute cypher query within a CASE WHEN THEN clause in Neo4j Cypher

I have a use case where I am trying to optimize my Neo4j db calls and code by using the RETURN CASE WHEN THEN clauses in Cypher to run different queries depending on the WHEN result. This is my example:
MATCH (n {email: 'abc123#abc.com'})
RETURN
CASE WHEN n.category='Owner' THEN MATCH '(n)-[r:OWNS]->(m)'
WHEN n.category='Dealer' THEN MATCH (n)-[r:SUPPLY_PARTS_FOR]->(m)
WHEN n.category='Mechanic' THEN MATCH (n)-[r:SERVICE]-(m) END
AS result;
I am not sure this is legal but this is what I want to achieve. I am getting syntax errors like Invalid input '>'. How can I achieve this in the best manner?
EDIT for possible APOC solution:
This was my plan before discovering the limitation of FOREACH...
MATCH (user:Person {email:{paramEmail}})
FOREACH (_ IN case when 'Owner' = {paramCategory} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
UNWIND coll AS vehicle
MATCH(v:Vehicles {name:vehicle})
CREATE UNIQUE (user)-[r:OWNS {since: timestamp()}]->(v)
SET r += paramVehicleProps
)
FOREACH (_ IN case when 'Mechanic' = {Category} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
….
)
FOREACH (_ IN case when 'Dealer' = {paramCategory} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
…...
)
RETURN user,
CASE {paramCategory}
WHEN 'Owner' THEN [(n)-[r:OWNS]->(m) | m and r]
WHEN 'Dealer' THEN [(n)-[r:SUPPLY_PARTS_FOR]->(m) | m]
WHEN 'Mechanic' THEN [(n)-[r:SERVICE]-(m) | m]
END AS result`,{
paramQueryObj: queryObj,
paramVehicles: makeVehicleArray,
paramVehicleProps: vehiclePropsArray,
paramSalesAgent: dealerSalesAgentObjarray,
paramWarehouseAgent: dealerWarehouseAgentObjarray
}).....
does anyone know to convert this using apoc.do.when()? note I need 'm' and 'r' in the first THEN.
You should still use a label in your first match, otherwise you get a full database scan and not a index lookup by email!!
for your query you can use pattern comprehensions:
MATCH (n:Person {email: 'abc123#abc.com'})
RETURN
CASE n.category
WHEN 'Owner' THEN [(n)-[r:OWNS]->(m) | m]
WHEN 'Dealer' THEN [(n)-[r:SUPPLY_PARTS_FOR]->(m) | m]
WHEN 'Mechanic' THEN [(n)-[r:SERVICE]-(m) | m] END
AS result;
It is also possible to use apoc.do.case: https://neo4j.com/labs/apoc/4.4/overview/apoc.do/apoc.do.case/
CALL apoc.do.case([
false,
'CREATE (a:Node{name:"A"}) RETURN a AS node',
true,
'CREATE (b:Node{name:"B"}) RETURN b AS node'
],
'CREATE (c:Node{name:"C"}) RETURN c AS node',{})
YIELD value
RETURN value.node AS node;

Neo4j: Creating Multiple Relationships Not Working

I am creating multiple relationships in one query. If a match is not found for the first relationship the second one is not created. If both matches exist then both relationships work.
Example:
Assume that 'MATCH1ID' below does not exist but 'MATCH2ID' does. Why does this happen?
DOES NOT WORK
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
MATCH (a)
WHERE a.Id = 'MATCH1ID'
CREATE (a)-[ar:Relationship1]->(NewRecord)
WITH NewRecord MATCH (b)
WHERE b.Id = 'MATCH2ID'
CREATE (b)-[br:Relationship2]->(NewRecord)
(If I switch the order of the node that does exist to be first in the order it works)
WORKS
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
MATCH (b)
WHERE b.Id = 'MATCH2ID'
CREATE (b)-[br:Relationship2]->(NewRecord)
WITH NewRecord
MATCH (a)
WHERE a.Id = 'MATCH1ID'
CREATE (a)-[ar:Relationship1]->(NewRecord)
You can try using a OPTIONAL MATCH instead of a simple MATCH:
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
OPTIONAL MATCH (a)
WHERE a.Id = 'MATCH1ID'
FOREACH(x IN (CASE WHEN a IS NULL THEN [] ELSE [1] END) |
CREATE (a)-[ar:Relationship1]->(NewRecord)
)
WITH NewRecord
OPTIONAL MATCH (b)
WHERE b.Id = 'MATCH2ID'
FOREACH(x IN (CASE WHEN b IS NULL THEN [] ELSE [1] END) |
CREATE (b)-[br:Relationship2]->(NewRecord)
)
Also, this query uses a trick with FORACH and CASE WHEN to handle conditions. In this case, the CREATE statement is executed only when a or b variables are not null.
See Neo4j: LOAD CSV - Handling Conditionals.

Consolidating & Collecting into one Record

I'm trying to write a cypher query where all related nodes are collected and returned under a key in a map:
{
name: 'chart',
CONTAINTED: [
{
name: 'id'
},
{
name: 'bloodpressure'
},
...
],
FOREIGNKEY: [
{
name: 'clientid'
}
]
}
I've attempted to do this with the following cypher query but it isn't quite right. Using the method below two records are returned rather than just one.
MATCH path=(table:tabPHI {name: 'client'})-[r]-(c)
WITH table as tab, type(r) as rel, collect(c) as col
CALL apoc.map.setKey(tab, rel, col) YIELD value as foo
return foo
MATCH (table:tabPHI {name: 'client'})-[r]-(c)
WITH table as tab, type(r) as rel, collect(c) as col
WITH tab, COLLECT(rel) AS keys, COLLECT(col) AS values
WITH keys(tab) as main_keys, [key IN keys(tab)|tab[key]] AS main_values, keys, values
CALL apoc.map.fromLists(main_keys + keys, main_values + values) YIELD value as foo
return foo
You're creating a single map for each relationship type in your query. You have to COLLECT(rel) at some point to get a single result for each tab.

Resources