Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
07-17-2020 08:34 AM
I have the following APOC trigger which contains a apoc procedure. This works as desired but I am wondering if I used a lot of WITH statements, is that the way to go? or is there a better way to refactor this code. I am still new to neo4j so not sure.
The control flow- Whenever the Enrollment node is created, it creates an SPerson node and depending on the size(number of SPerson nodes) it creates an enrolled or waitlist(if size>3, and deletes enrolled relation) relation with the course node.
The MATCH (e:Enrollment) DETACH DELETE e is to delete any 'enollment' node after creation. I don't want any 'enrollment' nodes in neo4j at the end. They are only here for starting the trigger. The nodes that have a purpose are the 'SPerson' nodes which will have properties(created from the enrollment nodes and enrollment nodes will then get deleted) and relations with the course node.
For Example
CREATE (:Enrollment { name: "Manish", cell: "99876", created_at: TIMESTAMP()});
#This will start the trigger and create a 'SPerson' node with a 'enrolled' relationship with the 'course' node.
My code
CALL apoc.trigger.add('loadEnrollments',
"UNWIND apoc.trigger.nodesByLabel($assignedLabels, 'Enrollment') AS node
MERGE (p1:SPerson { name: node.name, cell: node.cell, created_at: node.created_at})
WITH p1, node
MATCH (c:Course {name: 'Paradigm Shifting 101'})
WITH node
MATCH (n:SPerson)
WITH node, COUNT(n) as size
CALL apoc.do.when(
size>3,
'MATCH(p1:SPerson),(c:Course)
WHERE p1.name=node.name
CREATE (p1)-[:Waitlist]->(c)
WITH p1,c
MATCH (e:Enrollment) DETACH DELETE e',
'MATCH(p1:SPerson),(c:Course)
WHERE p1.name=node.name
CREATE (p1)-[:Enrolled]->(c)
WITH p1,c
MATCH (e:Enrollment) DETACH DELETE e', {node:node}) YIELD value
DETACH DELETE node",
{ phase: 'after' });
07-17-2020 09:05 AM
There's no way that's doing what you think it is.
WITH p1, node MATCH (c:Course {...}) MATCH (n:SPerson) ...
WITH
only the variables included in the WITH
clause will be available for subsequent commands.
MATCH (c:Course {name: 'Paradigm Shifting 101'})
does nothing.MATCH(p1:SPerson),(c:Course) WHERE p1.name=node.name CREATE (p1)-[:Waitlist]->(c)
will put that person on a waitlist for every course.I see what you're going for, and while I could rewrite your Cypher for you, I think that will be hurting more than helping. Work on the smaller pieces first, and make sure they're doing what you think, before trying to plug it all together.
07-18-2020 11:22 AM
Thank you Tony for the feedback. I really appreciate it. I rewrote my query. Would love to hear your thoughts.
CALL apoc.trigger.add('loadEnrollments',
"UNWIND apoc.trigger.nodesByLabel($assignedLabels, 'Enrollment') AS node
MERGE (p1:SPerson { name: node.name, cell: node.cell, created_at: node.created_at})
WITH p1, node
MATCH (n:SPerson)
WITH node, COUNT(n) as size
CALL apoc.do.when(
size>3,
'
MATCH(p1:SPerson),(c:Course)
WHERE p1.name=node.name AND c.name=\"Paradigm Shifting 101\"
CREATE (p1)-[:Waitlist]->(c)
WITH node
RETURN NULL',
'
MATCH(p1:SPerson),(c:Course)
WHERE p1.name=node.name AND c.name=\"Paradigm Shifting 101\"
CREATE (p1)-[:Enrolled]->(c)
WITH node
RETURN NULL', {node:node}) YIELD value
DETACH DELETE node",
{ phase: 'after' })
07-20-2020 04:55 PM
That's a step in the right direction.
UNWIND apoc.trigger.nodesByLabel($assignedLabels, 'Enrollment') AS node
MERGE (p1:SPerson { name: node.name, cell: node.cell, created_at: node.created_at})
WITH p1, node
MATCH (n:SPerson) # are you trying to get a total of all nodes with the :SPerson label?
WITH node, COUNT(n) as size
At this point, you only have 2 variables in context: node
, and size
, where size
will always be the count of MATCH(:SPerson)
. I suspect you are instead attempting to count the number of matching :SPerson
nodes, for which you only need the p1 variable.
Your existing query is again, doing nothing with the p1 variable you first create, and instead find it a second time within the do.when block.
Instead:
WITH p1, node, COUNT(p1) as size
CALL apoc.do.when( size>3 ,
'
MATCH (c:Course {name: \"Paradigm Shifting 101\"})
MERGE (p1)-[:Waitlist]->(c)
'
...
That should be all you really need there, to do what it looks like you're trying to do with the code, but several things still escape me. For instance, why does it matter the count of SPeople, and why don't you have the course-name in the :Enrollment node?
Since you've put some effort into it though, I'll give you something functional, but I strongly advise you carefully analyze why this works, and try to better understand exactly what yours was doing differently.
I'll assume the course-name is in the :Enrollment
node.
UNWIND apoc.trigger.nodesByLabel($assignedLabels, 'Enrollment') AS node
MERGE (p1:SPerson { name: node.name, cell: node.cell, created_at: node.created_at})
WITH p1, node
MATCH (c:Course {name: node.course_name})<-[:Enrolled]-(n:SPerson)
WITH node, p1, c, COUNT(n) as size
# DO WHEN size (number of currently enrolled) <= 4 (no more than 4 enrolled at one time?)
MERGE (p1)-[:Enrolled]->(c) // MERGE to prevent re-adding the same person multiple times
DETACH DELETE node
# ELSE
MERGE (p1)-[:Waitlist]->(c)
DETACH DELETE node
It seems you're struggling a bit with variables, and script context. Remember the following simple rules:
(variable :Thing)
, every matching node is available in the variable
.WITH
is only sometimes needed, and it specifies exactly which variables will stay in the context.
MATCH (a)-[r]-(b) WITH a
will drop r
and b
. Only a
will be able to be used in the rest of the script.MATCH
to find a reference to a node that you've previously had a reference to, you're doing something wrong. Worse, if you do a single DETACH
or DELETE
on that node, between the MATCH
statements for it, you're setting yourself up for some serious problems in the transaction.It looks like you're trying to handle a student request to enroll in a class. If we adjust some terms to reflect better graph design, things will be simplified. Labels are singular nouns, and rels are verbs.
(s:Student {id, name})
(c:Class {id, name, max_students})
(e:Enrollment {student_id, student_name, class_id})
(s)<-[:Applying]-(a)
(s)-[:Enrolled]->(c)
(s)-[:Waiting]->(c)
Rather than going all fancy with triggers, we can handle this with a MERGE when creating the Enrollment:
MATCH (s:Student: {id: 123}), (c:Class {id:456})<-[:Enrolled]-(current:Student)
MERGE (e:Enrollment {student_id: s.id, class_id: c.id})
WITH s, c, a, count(current) as numEnrolled
CALL apoc.do.when(
numEnrolled <=4,
"MERGE (s)-[:Enrolled]->(c)",
"MERGE (s)-[:Waiting]->(c)"
)
DETACH DELETE Enrollment;
This makes it clear that even the Enrollment label itself is unnecessary, if we handle this when attempting to create the Enrollment to begin with:
:params {student_id: 123, class_id: 456}
MATCH (s:Student: {id: $student_id}), (c:Class {id: $class_id})<-[:Enrolled]-(current:Student)
WITH s, c, count(current) as numEnrolled
CALL apoc.do.when(
numEnrolled <=4,
"MERGE (s)-[:Enrolled]->(c)",
"MERGE (s)-[:Waiting]->(c)"
)
All the sessions of the conference are now available online