CQL Syntax exception - neo4j

Neo.ClientError.Statement.SyntaxError: Invalid input ')': expected
whitespace or a relationship pattern (line 66, column 100 (offset:
1898)) "CREATE (z:Subscription{ subscriptionId: subs.subscriptionId,
startDate: subs.startDate, endDate:''})<-[r:ASSOCIATION]-(y:Person
{nationalIdentityNumber: subs.nationalIdentityNumber, name: subs.name,
surname: subs.surname, fathername: subs.fathername , nationality:
subs.nationality, passportNo: subs.passportNo, birthdate:
subs.birthdate})"
I want to create/merge nodes and relation that types are Person, Subscription and Line
If I had same subscription I should check to startDate, If new data's start date greater then old data; I sould create new Subscription and also change old subscription's end date.
UNWIND [{
msisdn:'99658321564',
name:'Lady',
surname:'Camble',
fatherName:'Aeron',
nationality:'EN',
passportNo:'PN-1234224',
birthDate:'12-05-1979',
nationalIdentityNumber:'112124224',
subscriptionId:'2009201999658321564',
startDate:'20-09-2019 12:00:12'
},{msisdn:'99658363275',
name:'John',
surname:'Mckeen',
fatherName:'Frank',
nationality:'EN',
passportNo:'PN-126587',
birthDate:'15-08-1998',
nationalIdentityNumber:'2548746542',
subscriptionId:'1506201999658363275',
startDate:'15-06-2019 13:00:12'}
{
msisdn:'99658321564',
name:'Lady',
surname:'Camble',
fatherName:'Aeron',
nationality:'EN',
passportNo:'PN-1234224',
birthDate:'12-05-1979',
nationalIdentityNumber:'112124224',
subscriptionId:'2009201999658321564',
startDate:'31-11-2019 12:00:12'
}
] as subs
MERGE (y:Person {nationalIdentityNumber: subs.nationalIdentityNumber, name: subs.name, surname: subs.surname, fathername: subs.fathername , nationality: subs.nationality, passportNo: subs.passportNo, birthdate: subs.birthdate })
MERGE (t:Subscription{subscriptionId:subs.subscriptionId })
MERGE (y)-[rel:ASSOCIATION]-(t)
ON MATCH SET
t.endDate = (case when t.startDate <subs.startDate then subs.startDate else ''
end)
MATCH (t:Subscription) where t.subscriprionId=subs.subscriprionId and
(CASE
WHEN t.endDate=subs.startDate then
CREATE (z:Subscription{ subscriptionId: subs.subscriptionId, startDate: subs.startDate, endDate:''})-[r:ASSOCIATION]-(y:Person {nationalIdentityNumber: subs.nationalIdentityNumber, name: subs.name, surname: subs.surname, fathername: subs.fathername , nationality: subs.nationality, passportNo: subs.passportNo, birthdate: subs.birthdate})
END)
RETURN y

UNWIND[...] as subs
MERGE (y:Person {nationalIdentityNumber: subs.nationalIdentityNumber, name: subs.name, surname: subs.surname, fatherName: subs.fatherName , nationality: subs.nationality, passportNo: subs.passportNo, birthDate: subs.birthDate })
MERGE (t:Subscription{subscriptionId:subs.subscriptionId,startDate:subs.startDate,endDate:''})
MERGE (y)-[rel:ASSOCIATION]-(t)
MERGE(x:Subscription{subscriptionId:subs.subscriptionId, endDate:''})
SET
x.endDate = (case when x.startDate < subs.startDate then subs.startDate else null end);
CQL should like this. Thanks my co-worker.

You're trying to have conditional Cypher clauses through a CASE statement, and that won't work. You can't do a nested CREATE (or any other Cypher clause) in a CASE.
You can however use a trick with FOREACH and CASE to mimic an if conditional. That should work in your case, as you want to only execute a CREATE under certain conditions (though since you already matched to the y node for the person, just reuse (y) in that CREATE instead of trying to define the entire node again from labels and properties, that won't work properly).
If you need more advanced conditional logic, that's available via conditional procs in APOC Procedures

Related

Neo4j/Cypher optionally create a Node and relationship based on whether a JSON object is empty or not

First off, I am using the GRANDstack with Flutter as my frontend. Not super relevant, but for context. I have a CreatePost mutation:
mutation CreatePost(\$body: String!, \$userId: ID!, \$imageReference: CreateImageReference!, \$mentions: [String!]) {
CreatePost(body: \$body, userId: \$userId, imageReference: \$imageReference, mentions: \$mentions) {
id,
body,
createdAt {
formatted
},
updatedAt {
formatted
},
user {
username
},
imageReference {
downloadURL
}
}
}
that is successfully sending my CreateImageReference input as JSON with name and downloadURL attributes. The custom input type:
input CreateImageReference {
name: String!
downloadURL: String!
}
in my schema file is straightforward and being accepted by apollo/graphql just fine. I am logging the "graphqlResponse" to monitor everything and there appears to be no issue up to this point.
I am attempting to "optionally create" this ImageReference node and a relationship to the Post node based on whether the $imageReference parameter is null or not. My custom mutation with cypher query without the addition of the ImageReference logic:
CreatePost(body: String!, userId: ID!, imageReference: CreateImageReference!, mentions: [String!]): Post
#cypher(
statement: """
MATCH (u:User {id: $userId})-[:MEMBER_OF]->(g:Group)
OPTIONAL MATCH (g)-[rel:NEWEST_POST]->(prevNewest:Post)
CREATE (u)-[:WROTE]->(p:Post {id: apoc.create.uuid(), body: $body, createdAt: datetime(), updatedAt: datetime()})<-[:NEWEST_POST]-(g)
MERGE (u)-[hp:HAS_PARTICIPATED]->(g)
ON MATCH SET hp.updatedAt = p.createdAt
ON CREATE SET hp.createdAt = p.createdAt, hp.updatedAt = p.createdAt
FOREACH(i in CASE WHEN NOT rel IS NULL THEN [1] ELSE [] END |
DELETE rel CREATE (p)-[:NEXT_POST]->(prevNewest))
WITH p
OPTIONAL MATCH (allMentionedUsers:User)
WHERE allMentionedUsers.id in $mentions
UNWIND allMentionedUsers as mentionedUser
MERGE (p)-[:MENTIONS]->(mentionedUser)
// Add CREATE ImageReference and
// Add CREATE relationship (HAS_ATTACHMENT) between ImageReference and Post here
RETURN p
""")
What is the most efficient way to check if the variable $imageReference IS NULL and do nothing if it is vs. run the CREATE node/relationship statements when NOT IS NULL:
CREATE (i:ImageReference {id: apoc.create.uuid(), name: $imageReference['name'], downloadURL: $imageReference['downloadURL']}
WITH i
CREATE (p)-[:HAS_ATTACHMENT]-(i)
Here is an attempt with CASE:
CASE $imageReference
WHEN null []
ELSE CREATE (i:ImageReference {
id: apoc.create.uuid(),
name: $imageReference['name'],
downloadURL: $imageReference['downloadURL'],
createdAt: datetime(),
updatedAt: datetime(),
deletedAt: null
})
CREATE (p)-[:HAS_ATTACHMENT]->(i)
WITH i END
that is throwing a "Neo4jError: Failed to invoke procedure apoc.cypher.doIt: Caused by: org.neo4j.exceptions.SyntaxException: Invalid input 'S': expected 'l/L' (line 10, column 3 (offset: 678))",.
Here is an attempt with FOREACH that is successfully creating the Post, but not the ImageReference with no errors to indicate why:
FOREACH (i in CASE WHEN NOT $imageReference IS NULL THEN [1] ELSE [] END |
CREATE (ir:ImageReference {
id: apoc.create.uuid(),
name: $imageReference['name'],
downloadURL: $imageReference['downloadURL'],
createdAt: datetime(),
updatedAt: datetime(),
deletedAt: null
})
CREATE (p)-[:HAS_ATTACHMENT]->(ir))
If I execute this part:
CREATE (ir:ImageReference {
id: apoc.create.uuid(),
name: $imageReference['name'],
downloadURL: $imageReference['downloadURL'],
createdAt: datetime(),
updatedAt: datetime(),
deletedAt: null
})
CREATE (p)-[:HAS_ATTACHMENT]->(ir))
without the CASE or FOREACH, it successfully creates the Node and relationship as expected, which is fine until I want to create a post without an ImageReference. Perhaps the solution is to just create two different queries?
You can use the APOC procedure apoc.do.when to perform if-then-else processing that can write to the DB (apoc.when can be used for read-only processing).
For example, your last snippet can be done this way:
...
CALL apoc.do.when(
$imageReference IS NOT NULL,
' CREATE (ir:ImageReference {
id: apoc.create.uuid(),
name: ref.name,
downloadURL: ref.downloadURL,
createdAt: datetime(),
updatedAt: datetime()
})
CREATE (p)-[:HAS_ATTACHMENT]->(ir)
RETURN ir',
'',
{ref: $imageReference, p: p}
) YIELD value
WITH p, value.ir AS ir
...

Neo4j Cypher query for a linked list to conditionally create a NEWEST_REPLY vs modifying NEWEST_REPLY to NEXT_REPLY

I have a linked list of replies off of a Post that looks like this:
With the "first" reply in the list having a NEWEST_REPLY relationship to Post and subsequent replies having a NEXT_REPLY relationship. The query to get the above graph:
MATCH (p:Post {id: $postId})-[:NEWEST_REPLY|NEXT_REPLY*]->(r:Reply)
return p, r
I want to create a cypher query that either
Creates a reply and creates the NEWEST_REPLY relationship when there are no replies OR
Creates a reply, deletes the current NEWEST_REPLY relationship, creates a NEXT_REPLY relationship to the previous NEWEST_REPLY and a NEWEST_REPLY relationship to the new Reply.
This statement:
MATCH (p:Post {id: $postId})-[rel:NEWEST_REPLY]->(previousNewestReply:Reply)
DELETE rel
CREATE (r:Reply { id: apoc.create.uuid(), body: $body, createdAt: datetime(), updatedAt: datetime() })
WITH r, p, previousNewestReply
MATCH (u:User)
WHERE u.id = $userId
CREATE (r)<-[:WROTE]-(u)
CREATE (r)<-[:NEWEST_REPLY]-(p)
CREATE (r)-[:NEXT_REPLY]->(previousNewestReply)
RETURN u, p
achieves number 2.
What I now need to do is conditionally run this statement if the rel in MATCH (p:Post {id: $postId})-[rel:NEWEST_REPLY]->(previousNewestReply:Reply) exists, but if it does not exist, just create NEWEST_REPLY for the first time as well as creating the reply and the User-[:WROTE]->Reply relationship. I'm new to cypher and digging into MERGE, CASE, predicate functions and apoc.when() and not sure which would be the simplest and most appropriate.
Here's an attempt at using CASE:
MATCH (p:Post {id: "db7ee38c-fe60-430e-a7c7-0b2514401343"})
RETURN
CASE EXISTS( (p)-[rel:NEWEST_REPLY]->(replies:Reply) )
WHEN true THEN DELETE rel CREATE (r:Reply { id: apoc.create.uuid(), body: "new with CASE1", createdAt: datetime(), updatedAt: datetime() }) WITH r, p, replies MATCH (u:User) WHERE u.id = "e14d409e-d970-4c5c-9cc7-3b224c774835" CREATE (r)<-[:WROTE]-(u) CREATE (r)<-[:NEWEST_REPLY]-(p) CREATE (r)-[:NEXT_REPLY]->(replies)
WHEN false THEN CREATE (r:Reply { id: apoc.create.uuid(), body: "new with CASE2", createdAt: datetime(), updatedAt: datetime() }) WITH r, p, previousNewestReply MATCH (u:User) WHERE u.id = "e14d409e-d970-4c5c-9cc7-3b224c774835" CREATE (r)<-[:WROTE]-(u) CREATE (r)<-[:NEWEST_REPLY]-(p) END
AS result;
And running into the following SyntaxError:
Invalid input 'r': expected whitespace, comment, '{', node labels, MapLiteral, a parameter, a parameter (old syntax), a relationship pattern, '(', '.', '[', '^', '*', '/', '%', '+', '-', "=~", IN, STARTS, ENDS, CONTAINS, IS, '=', '~', "<>", "!=", '<', '>', "<=", ">=", AND, XOR, OR, WHEN, ELSE or END (line 4, column 24 (offset: 145))
"WHEN true THEN DELETE rel CREATE (r:Reply { id: apoc.create.uuid(), body: "new with CASE1", createdAt: datetime(), updatedAt: datetime() }) WITH r, p, replies MATCH (u:User) WHERE u.id = "e14d409e-d970-4c5c-9cc7-3b224c774835" CREATE (r)<-[:WROTE]-(u) CREATE (r)<-[:NEWEST_REPLY]-(p) CREATE (r)-[:NEXT_REPLY]->(replies)"
My sense is that the logic I am attempting in either THEN statements is too complex for a CASE. Is there a more appropriate with to essentially do an if/else off of whether or not the NEWEST_REPLY relationship exists off of a specific Post?
[UPDATED]
This query should work for you:
MATCH (p:Post), (u:User)
WHERE p.id = $postId AND u.id = $userId
OPTIONAL MATCH (p)-[rel:NEWEST_REPLY]->(prevNewest:Reply)
CREATE (u)-[:WROTE]->(r:Reply {id: apoc.create.uuid(), body: "foo", createdAt: datetime(), updatedAt: datetime()})<-[:NEWEST_REPLY]-(p)
FOREACH(_ IN CASE WHEN rel IS NOT NULL THEN [1] END | DELETE rel CREATE (r)-[:NEXT_REPLY]->(prevNewest))
I asssume postId and userId are passed as parameters. Also, you should create indexes on :Post(di) and :User(id) to speed up the query.
You can do this to delete any existing [:NEWEST_REPLY] rels:
MATCH (p:Post {id: $postId})
OPTIONAL MATCH (p)-[rel:NEWEST_REPLY]->(previousNewestReply:Reply)
WITH p,previousNewestReply,
// create a collection of size 1 or 0
CASE WHEN NOT rel IS NULL THEN [rel] ELSE [] END AS toBeDeleted
// loop through the collection
FOREACH( tbd IN toBeDeleted | DELETE tbd )
WITH p,previousNewestReply
.....

Neo4j- APOC trigger fires without any reason

I have the following two triggers named 'loadEnrollments' and 'loadDeenrollments'.
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)
SET p1.status=2
WITH node
RETURN NULL',
'
MATCH(p1:SPerson),(c:Course)
WHERE p1.name=node.name AND c.name=\"Paradigm Shifting 101\"
CREATE (p1)-[:Enrolled]->(c)
SET p1.status=1
WITH node
RETURN NULL', {node:node}) YIELD value
DETACH DELETE node",
{ phase: 'after' })
CALL apoc.trigger.add('loadDeenrollments',
"
UNWIND apoc.trigger.nodesByLabel($assignedLabels, 'Deenrollment') AS node
MATCH (p1:SPerson {name: node.name, cell: node.cell})
MATCH (c:Course {name: 'Paradigm Shifting 101'})
CREATE (p1)-[:Deenrolled]->(c)
SET p1.status=3
WITH p1, node,c
MATCH (p1:SPerson {name: node.name, cell: node.cell})-[r:Enrolled]->(c)
DELETE r
DETACH DELETE node
WITH p1,c
MATCH (p1)-[r:Enrolled]->(c)
WITH COUNT(r) as k
CALL apoc.do.when(
k<3, '
MATCH (p1:SPerson)-[:Waitlist]->(c:Course)
WITH min(p1.created_at) AS min
MATCH (p1:SPerson),(c:Course)
WHERE p1.created_at = min
CREATE (p1)-[:Random]->(c)
RETURN p1,c',
'
MATCH (n:SPerson) RETURN n
',{k:k}
) YIELD value
RETURN NULL",
{ phase: 'after' })
When I load both of them and create a 'Enrollment' nodes by the following commands. The 'loadenrollment' trigger works as desired and create SPerson nodes for cat1,cat2 and cat3 and creates an 'enrolled' relationship with the 'course' node.
CREATE (:Enrollment { name: "cat1", cell: "123", created_at: TIMESTAMP()});
CREATE (:Enrollment { name: "cat2", cell: "123", created_at: TIMESTAMP()});
CREATE (:Enrollment { name: "cat3", cell: "123", created_at: TIMESTAMP()});
The Problem occurs when I create the 4th node
CREATE (:Enrollment { name: "cat4", cell: "123", created_at: TIMESTAMP()});
Ideally, it should create a SPerson node for cat4 and add a 'waitlist' relationship with the 'course' node.
But for some reason when I create that node it adds the 'waitlist' relationship but also adds the 'Random' relationship that I defined in the second trigger('loadDeenrollments'). This should never happen as this trigger would only trigger when I create a 'Deenrollment' node but for some reason it is executing that trigger.
Also, I tried adding just the 'loadEnrollments' trigger and it works as desired(obviously, as there is no 'loadDeenrollments' trigger) i.e. creating the four 'SPerson' nodes and with three nodes having 'enrolled' relationship and one having 'waitlist' relationship.
I don't know whats's wrong. Any help is appreciated!

Neo4j on Graphenedb How can I improve Cypher SPEED and what does Normailzing mean on Graphenedb

Here is my Cypher Query
MERGE (u:University {
name: $university, country: $country
})
MERGE (u)<-[:DEPARTMENT_IN]-(d:Department {
name: $department
})
MERGE (d)<-[:LEVEL_IN]-(l:Level {
name: $level
})
MERGE (l)<-[:STUDENT_OF]-(user:User {
firstName: $firstName,
lastName: $lastName,
password: $password,
email: $email,
username
dob: $dob,
id: $id
}) RETURN user, u, d, l
This works, but not very fast as what I anticipated. It takes up to 4.5 seconds to complete. Am I doing something wrong in the query, is there a faster way?
Also for the query
MATCH (u:University {name: $university})<-[DEPARTMENT_IN]-(d:Department) RETURN d
On Graphenedb message saying "This query has been normalized"
I don't really understand.
Thanks for your answers 😊
Profile of the Query

How to add unique data to a neo4j graph database

I am adding iteratively data to my neo4j database but I am stuck with how to overwrite or update existing data and to check whether the data does not already exist in there.
Basically I have a set of movies with their corresponding id's, e.g.:
[
{id: 'gameofthrones', genre: 'fantasy', release: '2017'},
{id: 'inception', genre: 'scifi', release: '2010'},
...
]
I can add the movies as follows:
CREATE
(m1:Movie {id: 'gameofthrones', genre: 'fantasy', release: '2017'}),
(m2:Movie {id: 'inception', genre: 'scifi', release: '2010'})
However, when I run the script two times, then it creates 4 nodes instead of keeping it at two nodes.
So my question is, how can I make sure that it checks whether the node id is already present, and if so overwrite it instead of creating a new node?
I tried (but only the properties get added)
// data
attributes['id'] = 'gameofthrones';
attributes['genre'] = 'fantasy';
...
// query
MERGE ( s:Movie {attributes}.id)
ON CREATE SET ( s:Movie {attributes} )
which I call in NodeJS as follows:
executeQuery(queryStr, {"attributes": attributes})
// cypher (nodejs)
function executeQuery(queryStr, params) {
var qq = Q.defer();
db.cypher({
query: queryStr,
params: params,
}, function (error, results) {
if (error) {
qq.reject(error);
} else {
if (results.length != 0) {
qq.resolve(true);
} else {
qq.resolve(false);
}
};
});
return qq.promise;
};
you must change your query to this
MERGE ( s:Movie {attributes}.id)
ON CREATE SET s += {attributes}
ON MATCH SET s += {attributes} // optional
this should work, but you should use apoc.map.clean() so you do not set the id twice, which can cause some problems.
MERGE ( s:Movie {attributes}.id)
ON CREATE SET s += apoc.map.clean({attributes},['id'],[])
You can achieve this with MERGE clause as follows
MERGE (m1:Movie {id: 'gameofthrones'})
ON CREATE SET m1.genre = 'fantasy', m1.release = '2017'
MERGE (m2:Movie {id: 'inception'})
ON CREATE SET m2.genre: 'scifi', m2.release = '2010'
Ideally you want to create queries with parameters instead of literal strings. You can achieve this if you user apoc.load.json
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1.genre = item.genre, m1.release = item.release
example for dynamic properties with apoc functions:
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1 += apoc.map.clean(item,['id'],[])
or if you do not have apoc plugin:
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1 += item
note that id will first be merged and later updated with ON CREATE SET and you want to avoid writing a single property twice, using apoc and above query we can achieve that

Resources