I want to add a "created by" relationship on nodes in my database. Any node should be able of having this relationship but there can never be more than one.
Right now my query looks something like this:
MATCH (u:User {email: 'my#mail.com'})
MERGE (n:Node {name: 'Node name'})
ON CREATE SET n.name='Node name', n.attribute='value'
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(u)
RETURN n
As I have understood Cypher there is no way to achieve what I want, the current query will only make sure there are no unique relationships based on TWO nodes, not ONE. So, this will create more CREATED_BY relationships when run for another User and I want to limit the outgoing CREATED_BY relationship to just one for all nodes.
Is there a way to achieve this without running multiple queries involving program logic?
Thanks.
Update
I tried to simplyfy the query by removing implementation details, if it helps here's the updated query based on cybersams response.
MERGE (c:Company {name: 'Test Company'})
ON CREATE SET c.uuid='db764628-5695-40ee-92a7-6b750854ebfa', c.created_at='2015-02-23 23:08:15', c.updated_at='2015-02-23 23:08:15'
WITH c
OPTIONAL MATCH (c)
WHERE NOT (c)-[:CREATED_BY]-()
CREATE (c)-[:CREATED_BY {date: '2015-02-23 23:08:15'}]->(u:User {token: '32ba9d2a2367131cecc53c310cfcdd62413bf18e8048c496ea69257822c0ee53'})
RETURN c
Still not working as expected.
Update #2
I ended up splitting this into two queries.
The problem I found was that there was two possible outcomes as I noticed.
The CREATED_BY relationship was created and (n) was returned using OPTIONAL MATCH, this relationship would always be created if it didn't already exist between (n) and (u), so when changing the email attribute it would re-create the relationship.
The Node (n) was not found (because of not using OPTIONAL MATCH and the WHERE NOT (c)-[:CREATED_BY]-() clause), resulting in no relationship created (yay!) but without getting the (n) back the MERGE query looses all it's meaning I think.
My Solution was the following two queries:
MERGE (n:Node {name: 'Name'})
ON CREATE SET
SET n.attribute='value'
WITH n
OPTIONAL MATCH (n)-[r:CREATED_BY]-()
RETURN c, r
Then I had program logic check the value of r, if there was no relationship I would run the second query.
MATCH (n:Node {name: 'Name'})
MATCH (u:User {email: 'my#email.com'})
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(u)
RETURN n
Unfortunately I couldn't find any real solution to combining this in one single query with Cypher. Sam, thanks! I've selected your answer even though it didn't quite solve my problem, but it was very close.
This should work for you:
MERGE (n:Node {name: 'Node name'})
ON CREATE SET n.attribute='value'
WITH n
OPTIONAL MATCH (n)
WHERE NOT (n)-[:CREATED_BY]->()
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(:User {email: 'my#mail.com'})
RETURN n;
I've removed the starting MATCH clause (because I presume you want to create a CREATED_BY relationship even when that User does not yet exist in the DB), and simplified the ON CREATE to remove the redundant setting of the name property.
I have also added an OPTIONAL MATCH that will only match an n node that does not already have an outgoing CREATED_BY relationship, followed by a CREATE UNIQUE clause that fully specifies the User node.
Related
So this is a very basic question. I am trying to make a cypher query that creates a node and connects it to multiple nodes.
As an example, let's say I have a database with towns and cars. I want to create a query that:
creates people, and
connects them with the town they live in and any cars they may own.
So here goes:
Here's one way I tried this query (I have WHERE clauses that specify which town and which cars, but to simplify):
MATCH (t: Town)
OPTIONAL MATCH (c: Car)
MERGE a = ((c) <-[:OWNS_CAR]- (p:Person {name: "John"}) -[:LIVES_IN]-> (t))
RETURN a
But this returns multiple people named John - one for each car he owns!
In two queries:
MATCH (t:Town)
MERGE a = ((p:Person {name: "John"}) -[:LIVES_IN]-> (t))
MATCH (p:Person {name: "John"})
OPTIONAL MATCH (c:Car)
MERGE a = ((p) -[:OWNS_CAR]-> (c))
This gives me the result I want, but I was wondering if I could do this in 1 query. I don't like the idea that I have to find John again! Any suggestions?
It took me a bit to wrap my head around why MERGE sometimes creates duplicate nodes when I didn't intend that. This article helped me.
The basic insight is that it would be best to merge the Person node first before you match the towns and cars. That way you won't get a new Person node for each relationship pattern.
If Person nodes are uniquely identified by their name properties, a unique constraint would prevent you from creating duplicates even if you run a mistaken query.
If a person can have multiple cars and residences in multiple towns, you also want to avoid a cartesian product of cars and towns in your result set before you do the merge. Try using the table output in Neo4j Browser to see how many rows are getting returned before you do the MERGE to create relationships.
Here's how I would approach your query.
MERGE (p:Person {name:"John"})
WITH p
OPTIONAL MATCH (c:Car)
WHERE c.licensePlate in ["xyz123", "999aaa"]
WITH p, COLLECT(c) as cars
OPTIONAL MATCH (t:Town)
WHERE t.name in ["Lexington", "Concord"]
WITH p, cars, COLLECT(t) as towns
FOREACH(car in cars | MERGE (p)-[:OWNS]->(car))
FOREACH(town in towns | MERGE (p)-[:LIVES_IN]->(town))
RETURN p, towns, cars
I wrote a script to to batch create a bunch of relationship in neo4j. Here is the cypher:
:param batch => [{startId: 'abc123', endId: 'abc321'}, {startId: 'abc456', endId: 'abc654']
UNWIND $batch as row
MATCH (from {id: row.startId}
MATCH (to {id: row.endId}
CREATE (from)-[rel:HAS]->(to)
RETURN rel
The problem that there might be some startId/endId entries that don't match any nodes and are silently ignore. Is there a way to return the list of rows that don't match any nodes and create the relationship for the nodes that do match?
I tried OPTIONAL MATCH to fail-fast as soon an id doesn't find a startId/endId however, the query execution was really slow.
First of all, you should always try to specify a label for the node that is used to kick off a MATCH (unless the MATCH pattern uses any already-bound nodes). Otherwise, every single node in the DB must be scanned. In addition, you should consider using indexes to speed up your MATCHs (but, again, you'd need to specify the labels).
Here is a query that uses the APOC procedure apoc.do.when to create a new relationship when appropriate. It returns each row and the corresponding new relationship (or NULL if either node is not found):
UNWIND $batch as row
OPTIONAL MATCH (from:Foo {id: row.startId})
OPTIONAL MATCH (to:Foo {id: row.endId})
CALL apoc.do.when(
from IS NOT NULL AND to IS NOT NULL,
'CREATE (from)-[rel:HAS]->(to) RETURN rel',
'RETURN NULL AS rel',
{from: from, to: to}) YIELD value
RETURN row, value.rel AS rel
I have the following Neo4J Cypher query:
MATCH (u:User {uid: $userId})
UNWIND $contextNames as contextName
MERGE (context:Context {name:contextName.name,by:u.uid,uid:contextName.uid})
ON CREATE SET context.timestamp=$timestamp
MERGE (context)-[:BY{timestamp:$timestamp}]->(u)
The last string always creates a new relation between the context and the u node. However, what if I just want to update it? How do I integrate this logic into the query above?
Do I have to add WITH context,u before the MERGE and then add rel:BY into the query?
Or do MATCH (context)-[rel:BY.... and then update the rel?
Just looking for the most efficient "best practices" way to do that.
Thanks!
There are two possible situation which might occur:
A relation between context and u is already present
A relation between context and u is not yet present (this will happen when contextwas just created by merge)
When you run the following line
MERGE (context)-[:BY{timestamp:$timestamp}]->(u)
Neo4j will check if there is already a relation BY in place between context and u with the given timestamp value. If yes, no new relation will be created. I guess that the timestamp is not a proper identifier for matching a relation, especially since you write that you want to update it. Therefore, I recommend to update the query in the following way:
MATCH (u:User {uid: $userId})
UNWIND $contextNames as contextName
MERGE (context:Context {name:contextName.name,by:u.uid,uid:contextName.uid})
ON CREATE SET context.timestamp=$timestamp
MERGE (context)-[by:BY]->(u)
SET by.timestamp=$timestamp
This way, a relation will be created if not already present. Either way, the timestamp will be set to the specified value.
I want to restrict the relationships getting created , if already created between the existing nodes. Right now its getting created multiple times? Is there any command similar to 'merge' for restricting duplicate relationships?Thanks for any suggestion
User MERGE instead of CREATE while creating relationships.
MERGE (Product)-[:listed_under_category]->(level3)
MERGE (level3)-[:child_of]->(level2)
hey i think the mix up but i need more information. Is that merge, merges the the whole statement. so if he does not find a "match" for the whole statement the database "creates" the whole statement.
So when you type something like merge (a)-[]->(b)-[]->(q)
if you already have (a)-[]->(b)
he will recreate (a)-[]->(b)-[]->(q) and you have in the db (a)-[]->(b)-[]->(q) , (a)-[]->(b)
but if you type merge (a)-[]->(b) merge(b)-[]->(q)
and you know that you have the nodes. Neo4j merge will create the relations if there are no relations. and match if there are relations
If I understand your question, CREATE UNIQUE may be the solution you are looking for. For example:
create (a:Ex {name: 'A'})-[r:inside]->(b:Ex {name: 'B'}) return a, b, r
sets up your original (a)-[]->(b) relationship. Then to extend the relationship in the manner you proposed ....
match (a)-[:inside]->(b) where a.name = 'A' and b.name = 'B'
create unique (a)-[:inside]->(b)-[:inside]->(q:Ex {name: 'Q'})
return a, b, q
CREATE UNIQUE also works if the relationships are different:
match (a)-[:inside]->(b) where a.name = 'A' and b.name = 'B'
create unique (a)-[:inside]->(b)-[:under]->(q:Ex {name: 'Q'})
return a, b, q
The MERGE clause also avoids duplicating relationships.
I'm trying to implement a soft delete in Neo4j. The graph described in Cypher from Alice's viewpoint is as such:
(clyde:User)<-[:FOLLOWS]-(alice:User)-[:LIKES]->(bob:User)
Instead of actually deleting a node and its relationships, I'm
changing its label so it can no longer be looked up directly, i.e. dropping its User label and adding a _User label (notice the underscore)
replacing its relationships so it can't be reached anymore by my normal queries, e.g. deleting its :FOLLOWS relationships and replacing it with :_FOLLOWS relationships.
So this is basically the equivalent of moving a row to an archiving table in a relational database. I figured this is a pretty efficient approach because you're effectively never visiting the parts of the graph that have been soft-deleted. Also, you don't have to modify any of your existing queries.
The result of soft-deleting Alice should be this:
(clyde:User)<-[:_FOLLOWS]-(alice:_User)-[:_LIKES]->(bob:User)
My first attempt at the query was this:
match (user:User {Id: 1})
optional match (user)-[follows:FOLLOWS]->(subject)
remove user:User set user:_User
delete follows
create (user)-[:_FOLLOWS]->(subject);
The problem is that when this user is not following anyone, the query tries to create a relationship between user and null because the second match is optional, so it gives me this error: Other node is null.
My second attempt was this:
match (user:User {Id: 1})
remove user:User set user:_User
optional match (user)-[follows:FOLLOWS]->(subject)
foreach (f in filter(f in collect({r: follows, n: subject}) where f.r is not null) | delete f.r create (user)-[:_FOLLOWS]->(f.n));
So I'm putting the relationship and the subject into a map, collecting these maps in a collection, throwing every "empty" map away and looping over the collection. But this query gives me this error:
SyntaxException: Invalid input '.': expected an identifier character, node labels, a property map, whitespace or ')' (line 1, column 238)
Does anyone know how I can fix this?
Thanks,
Jan
Could you change the label first and then match for relationships? Then you should be able to use 'non-optional' match, and not have to deal with the cases where there are no follows relationships, something like
MATCH (user:User {Id: 1})
REMOVE user:User SET user:_User
WITH user
MATCH (user)-[follows:FOLLOWS]->(subject)
DELETE follows
CREATE (user)-[:_FOLLOWS]->(subject)
Or you could carry the user, follows and subject and filter on where subject is not null. Something like
MATCH (user:User {Id: 1})
OPTIONAL MATCH (user)-[follows:FOLLOWS]->(subject)
REMOVE user:User SET user:_User
WITH user, follows, subject
WHERE subject IS NOT NULL
DELETE follows
CREATE (user)-[:_FOLLOWS]->(subject)
Edit:
If the problem is that you want to do this for more than one kind of relationship, then you could try
MATCH (user:User {Id: 1})
REMOVE user:User SET user:_User
WITH user
MATCH (user)-[f:FOLLOWS]->(other)
DELETE f
CREATE (user)-[:_FOLLOWS]->(other)
WITH user LIMIT 1
MATCH (user)-[l:LIKES]->(other)
DELETE l
CREATE user-[:_LIKES]->(other)
You can keep extending it with other relationship types, just be sure to limit user when you carry, since multiple matches (user)-[r]->(other) means there are multiple results for user, or you'll run the next query part multiple times.
I don't think there is a generic way to do it in cypher since you can't dynamically build the relationship type (i.e. CREATE (a)-[newRel:"_"+type(oldRel)]->(b) doesn't work)
Is something like that what you are looking for or am I misunderstanding your question?