Neo4j Cypher : How to set StartNode or endNode of a relationship? - neo4j

Let's say we have two nodes n and m
Is it possible to set m as the startNode for all Relationship with n as the StartNode n-[r]->()
The relationships can have different types.
Is it possible using only one cypher request?

No, you can't re-assign the start node for a certain relationship. What you can do is delete that relationship, and then create new ones that point where you want them to go.
For example:
MATCH (n { id: "startpoint"})-[r]->(), (m {id: "endpoint"})
MERGE (n)-[:newRelationship]->(m)
DELETE r;
This query would have to get much more complicated if the type of :newRelationship could change depending on r

Example Data:
CREATE CONSTRAINT ON (city:City) ASSERT city.name IS UNIQUE;
CREATE CONSTRAINT ON (state:State) ASSERT state.name IS UNIQUE;
MERGE (pb:City {name: 'Paderborn'})
MERGE (state1:State {name: 'Bavaria'})
MERGE (state2:State {name: 'North Rhine-Westphalia'})
MERGE (pb)-[:LOCATED_IN]->(state1);
The following statement will remove the existing relationship and create a new one:
MATCH (n { name: "Paderborn"})-[r]->(), (state {name: "Bavaria"})
MERGE (n)-[:LOCATED_IN]->(state)
DELETE r;

Related

Match paths of node types where nodes may have cycles

I'm trying to find a match pattern to match paths of certain node types. I don't care about the type of relation. Any relation type may match. I only care about the node types.
Of course the following would work:
MATCH (n)-->(:a)-->(:b)-->(:c) WHERE id(n) = 0
But, some of these paths may have relations to themselves. This could be for :b, so I'd also like to match:
MATCH (n)-->(:a)-->(:b)-->(:b)-->(:c) WHERE id(n) = 0
And:
MATCH (n)-->(:a)-->(:b)-->(:b)-->(:b)-->(:c) WHERE id(n) = 0
I can do this with relations easily enough, but I can't figure out how to do this with nodes, something like:
MATCH (n)-->(:a)-->(:b*1..)-->(:c) WHERE id(n) = 0
As a practical example, let's say I have a database with people, cars and bikes. The cars and bikes are "owned" by people, and people have relationships like son, daughter, husband, wife, etc. What I'm looking for is a query that from a specific node, gets all nodes of related types. So:
MATCH (n)-->(:person*1..)-->(:car) WHERE Id(n) = 0
I would expect that to get node "n", it's parents, grandparents, children, grandchildren, all recursively. And then of those people, their cars. If I could assume that I know the full list of relations, and that they only apply to people, I could get this to work as follows:
MATCH
p = (n)-->(:person)-[:son|daughter|husband|wife|etc*0..]->(:person)-->(:car)
WHERE Id(n) = 0
RETURN nodes(p)
What I'm looking for is the same without having to specify the full list of relations; but just the node label.
Edit:
If you want to find the path from one Person node to each Car node, using only the node labels, and assuming nodes may create cycles, you can use apoc.path.expandConfig.
For example:
MERGE (mark:Person {name: "Mark"})
MERGE (lju:Person {name: "Lju"})
MERGE (praveena:Person {name: "Praveena"})
MERGE (zhen:Person {name: "Zhen"})
MERGE (martin:Person {name: "Martin"})
MERGE (joe:Person {name: "Joe"})
MERGE (stefan:Person {name: "Stefan"})
MERGE (alicia:Person {name: "Alicia"})
MERGE (markCar:Car {name: "Mark's car"})
MERGE (ljuCar:Car {name: "Lju's car"})
MERGE (praveenaCar:Car {name: "Praveena's car"})
MERGE (zhenCar:Car {name: "Zhen's car"})
MERGE (zhen)-[:CHILD_OF]-(mark)
MERGE (praveena)-[:CHILD_OF]-(martin)
MERGE (praveena)-[:MARRIED_TO]-(joe)
MERGE (zhen)-[:CHILD_OF]-(joe)
MERGE (alicia)-[:CHILD_OF]-(joe)
MERGE (zhen)-[:CHILD_OF]-(mark)
MERGE (anthony)-[:CHILD_OF]-(rik)
MERGE (martin)-[:CHILD_OF]-(mark)
MERGE (stefan)-[:CHILD_OF]-(zhen)
MERGE (lju)-[:CHILD_OF]-(stefan)
MERGE (markCar)-[:OWNED]-(mark)
MERGE (ljuCar)-[:OWNED]-(lju)
MERGE (praveenaCar)-[:OWNED]-(praveena)
MERGE (zhenCar)-[:OWNED]-(zhen)
Running a query:
MATCH (n:Person{name:'Joe'})
CALL apoc.path.expandConfig(n, {labelFilter: "Person|/Car", uniqueness: "NODE_GLOBAL"})
YIELD path
RETURN path
will return four unique paths from Joe node to the four car nodes. There are several options for uniqueness of the path, see uniqueness
The /CAR makes it a Termination label, i.e. returned paths are only up to this given label.

Conditional partial merge of pattern into graph

I'm trying to create a relationship that connects a person to a city -> state -> country without recreating the city/state/country nodes and relationships if they do already exist - so I'd end-up with only one USA node in my graph for example
I start with a person
CREATE (p:Person {name:'Omar', Id: 'a'})
RETURN p
then I'd like to turn this into an apoc.do.case statement with apoc
or turn it into one merge statement using unique the constraint that creates a new node if no node is found or otherwise matches an existing node
// first case where the city/state/country all exist
MATCH (locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality)-[:SITUATED_IN]->(adminArea)-[:SITUATED_IN]->(country)
return p
// second case where only state/country exist
MATCH (adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea)-[:SITUATED_IN]->(country)
return p
// third case where only country exists
MATCH (country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country)
return p
// last case where none of city/state/country exist, so I have to create all nodes + relations
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
return p
The key here is I only want to end-up with one (California)->(USA). I don't want those nodes & relationships to get duplicated
Your queries that use MATCH never specify which Person you want. Variable names like p only exist for the life of a query (and sometimes not even that long). So p is unbound in your MATCH queries, and can result in your MERGE clauses creating empty nodes. You need to add MATCH (p:Person {Id: 'a'}) to the start of those queries (assuming all people have unique Id values).
It should NOT be the responsibility of every single query to ensure that all needed localities exist and are connected correctly -- that is way too much complexity and overhead for every query. Instead, you should create the appropriate localities and inter-locality relationships separately -- before you need them. If fact, it should be the responsibility of each query that creates a locality to create all the relationships associated with it.
A MERGE will only not create the specified pattern if every single thing in the pattern already exists, so to avoid duplicates a MERGE pattern should have at most 1 thing that might not already exist. So, a MERGE pattern should have at most 1 relationship, and if it has a relationship then the 2 end nodes should already be bound (by MATCH clauses, for example).
Once the Locality nodes and the inter-locality relationships exist, you can add a person like this:
MATCH (locality:Locality {name: "San Diego"})
MERGE (p:Person {Id: 'a'}) // create person if needed, specifying a unique identifier
ON CREATE SET p.name = 'Omar'; // set other properties as needed
MERGE (p)-[:SITUATED_IN]->(locality) // create relationship if necessary
The above considerations should help you design the code for creating the Locality nodes and the inter-locality relationships.
Finally, the solution I used is much simpler, it's a series of merges.
match (person:Person {Id: 'Omar'}) // that should be present in the graph
merge (country:Country {name: 'USA'})
merge (state:State {name: 'California'})-[:SITUATED_IN]->(country)
merge (city:City {name: 'Los Angeles'})-[:SITUATED_IN]->(state)
merge (person)-[:SITUATED_IN]->(city)
return person;

How to avoid duplicate nodes when importing JSON into Neo4J

Let's say I have a JSON containing relationships between people:
{
[
{
"name": "mike",
"loves": ["karen", "david", "joy"],
"loved": ["karen", "joy"]
},
{
"name": "karen",
"loves": ["mike", "david", "joy"],
"loved": ["mike"]
},
{
"name": "joy",
"loves": ["karen"],
"loved": ["karen", "david"]
}
]
}
I want to import nodes and relationships into a Neo4J DB. For this sample, there's only one relationship ("LOVES") and the 2 lists each user has just control the arrow's direction. I use the following query to import the JSON:
UNWIND {json} as person
CREATE (p:Person {name: person.username})
FOREACH (l in person.loves | MERGE (v:Person {name: l}) CREATE (p)-[:LOVES]->(v))
FOREACH (f in person.loved | MERGE (v:Person {name: f}) CREATE (v)-[:LOVES]->(p))
My problem is that I now have duplicate nodes (i.e. 2 nodes with {name: 'karen'}). I know I could probably use UNIQUE if I insert records one at a time. But what should I use here when importing a large JSON? (to be clear: the name property would always be unique in the JSON - i.e., there are no 2 "mikes").
[EDITED]
Since you cannot assume that a Person node does not yet exist, you need to MERGE your Person nodes everywhere.
If there is no need to use your loved data (that is, if the loves data is sufficient to create all the necessary relationships):
UNWIND {json} as person
MERGE (p:Person {name: person.name})
FOREACH (l in person.loves | MERGE (v:Person {name: l}) CREATE (p)-[:LOVES]->(v))
On the other hand, if the loved data is needed, then you need to use MERGE when creating the relationships as well (since any relationship might already exist).
UNWIND {json} as person
MERGE (p:Person {name: person.name})
FOREACH (l in person.loves | MERGE (v:Person {name: l}) MERGE (p)-[:LOVES]->(v))
FOREACH (f in person.loved | MERGE (v:Person {name: f}) MERGE (v)-[:LOVES]->(p))
In both cases, you should create an index (or uniqueness constraint) on :Person(name) to speed up the query.

Cypher Query to return x Number of a particular type of node

Lets say we have a Neo4j graph such as (Brand)-[:from]->(Post)<-[:likes]-(Person).
How can I return a cypher query which will have a minimum number of brand posts, say 3. I want this to be scalable and not dependent on a specific property attribute value.
Hence the results would return at least 3 instances of the Brand nodes, as well as maybe 5 from Post and 15 from Person.
I have tried a few different things:
1.) Declare several variable names for each brand (not scalable)
Match (b:Brand)-[]->(p:Post)<-[]-(per:Person)
Match (b1:Brand)-[]->(p1:Post)<-[]-(per2:Person)
Match (b2:Brand)-[]->(p2:Post)<-[]-(per3:Person)
return b,b1,b2,p,p1,p2,per,per2,per3
limit 30
This didn't work because it essentially return the same as
Match (b:Brand)-[]->(p:Post)<-[]-(per:Person)
return b,p,per
limit 30
2.) Use a foreach some
Match (b:Brand) WITH collect (distinct b) as bb
FOREACH (b in bb[0..3] | MATCH (b)-[]->(p:Post)<-[]-(per:Person))
RETURN b, p, per LIMIT 40
This didn't work because you can't use Match inside a Foreach call.
The only way I know how to do this is to declare a where clause with their unique property brand name values which is not scalable. It looks like this:
Match (b:Brand)-[]->(p:Post)<-[]-(per:Person)
where b.brand = "b1" OR b.brand ="b2" or b.brand = "b3"
Return b,p,per
Limit 30
However the above still doesn't even return what I want.
Please help. Here is a quick graph to test on:
Create (b1:Brand {brand:'b1'})
Create (b2:Brand {brand:'b2'})
Create (b3:Brand {brand:'b3'})
Create (p1:Post {id: "001",message: "foo"})
Create (p2:Post {id: "002",message: "bar"})
Create (p3:Post {id: "003",message: "baz"})
Create (p4:Post {id: "004",message: "raz"})
Create (per1:Person {id: "001",name: "foo"})
Create (per2:Person {id: "002",name: "foo"})
Create (per3:Person {id: "003",name: "foo"})
Create (per4:Person {id: "004",name: "foo"})
Create (per5:Person {id: "005",name: "foo"})
Create (per6:Person {id: "006",name: "foo"})
Create (per7:Person {id: "007",name: "foo"})
Merge (b1)-[:FROM]->(p1)
Merge (b1)-[:FROM]->(p2)
Merge (b2)-[:FROM]->(p3)
Merge (b3)-[:FROM]->(p4)
Merge (per1)-[:LIKES]->(p1)
Merge (per1)-[:LIKES]->(p2)
Merge (per1)-[:LIKES]->(p3)
Merge (per2)-[:LIKES]->(p1)
Merge (per2)-[:LIKES]->(p4)
Merge (per3)-[:LIKES]->(p3)
Merge (per4)-[:LIKES]->(p1)
Merge (per5)-[:LIKES]->(p2)
Merge (per6)-[:LIKES]->(p1)
Merge (per6)-[:LIKES]->(p2)
Merge (per6)-[:LIKES]->(p3)
Merge (per6)-[:LIKES]->(p4)
Merge (per7)-[:LIKES]->(p4)
You can use the unwind instead of foreach:
Match (b:Brand) WITH collect (distinct b) as bb
UNWIND bb[0..3] as b
MATCH (b)-[]->(p:Post)<-[]-(per:Person)
RETURN b, p, per LIMIT 40
Or combine with and limit:
MATCH (b:Brand) WITH distinct b LIMIT 3
MATCH (b)-[]->(p:Post)<-[]-(per:Person)
RETURN b, p, per LIMIT 40

Neo4j IN clause case-insensitive

I created a node in neo4j db with this command:
CREATE (n:Person {Names: 'A', 'B', 'C'}) RETURN n;
CREATE (n:Person {Names: 'D'}) RETURN n;
Now, I want to make a query to retrieve a person has one of the name in a specific list.
I have this list : ['a', 'c'] -> It will return the 1st person
I know I have to use IN clause for this query, but I don't know how to achieve it.
I would like to write the command in Neo4j DB Browser and Neo4j Client .Net.
Can anyone help me please ?
Thank you.
1) You have an incorrect query to add nodes. It should be like this:
CREATE (n:Person {Names: ['A', 'B', 'C']}) RETURN n;
CREATE (n:Person {Names: ['D']}) RETURN n;
2) To search for occurrences of at least one value from the list, you can use the predicate ANY:
WITH ['a', 'D'] as Names
MATCH (P:Person)
WITH P, extract(name IN P.Names | LOWER(name)) as lowNames
WHERE ANY(n IN Names WHERE LOWER(n) IN lowNames)
RETURN P
Update. It is not excluded that it is better way in the spirit of "graph" is change the model. If we assume that the names can be repeated with different persons, then for each name it is necessary to add a node, and add a relationship with the person:
Create query:
MERGE (P1:Person {id:'P1'})
MERGE (P2:Person {id:'P2'})
MERGE (P3:Person {id:'P3'})
MERGE (N1:Name {name:'A'})
MERGE (N2:Name {name:'B'})
MERGE (N3:Name {name:'C'})
MERGE (N4:Name {name:'D'})
MERGE (P1)-[:has_name]->(N1)
MERGE (P1)-[:has_name]->(N2)
MERGE (P1)-[:has_name]->(N3)
MERGE (P2)-[:has_name]->(N4)
MERGE (P3)-[:has_name]->(N1)
MERGE (P1)-[:has_name]->(N4)`
And a query to search persons:
// Find the desired names
WITH ['a', 'D'] as Names
WITH extract(name IN Names | LOWER(name)) as lowerNames
MATCH (N:Name) WHERE LOWER(N.name) IN lowerNames
// For each name, we find person
WITH N
MATCH (P:Person)-[:has_name]->(N)
RETURN collect(distinct P)

Resources