Neo4j:Delete all relationships except for those in list - neo4j

I am trying to delete all relationships to a node except those that are in a list. I have already create a node (:Person {name: 'John'}) and 4 other nodes (:Car). Then I MERGE all the car nodes to the person node. I then want to delete all the relationships for the person node except for those in a list (shown below)
UNWIND [{name:'test1'}, {name:'test2'}] AS test
MATCH (p:Person {name:'John'})
OPTIONAL MATCH (p)-[d:DRIVES]->(c:Car)
WHERE NOT EXISTS((p)-[:DRIVES]->(c:Car {name:test.name}))
DELETE d
RETURN p
However the query above deletes all relationships but when I reduce the list to include only 1 car node, the above query works (i.e. the query only works when the list contains only 1 node and doesn't work when the list is larger). I am not sure why this is the case.
I am using neo4j 4.1.
Thanks in advance.

This should work:
WITH ['test1', 'test2'] AS tests
MATCH (p:Person {name: 'John'})
OPTIONAL MATCH (p)-[d:DRIVES]->(c:Car)
WHERE NOT c.name IN tests
DELETE d
RETURN p
and also this:
WITH ['test1', 'test2'] AS tests
MATCH (p:Person {name: 'John'})
FOREACH(x IN [(p)-[d:DRIVES]->(c:Car) WHERE NOT c.name IN tests | d] | DELETE x)
RETURN p

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.

How to get tree structure that has access rights on each node

I have a tree structure like a folder structure so with a project with nested project without a depth limit, each node has access rights on them.
Here is my graph:
Here is my query:
MATCH (a:Account {name: "bob"})-[r:VIEWER | EDITOR]->(c:Project)
MATCH (c)<-[:IS_PARENT*]-(p)
WHERE (p)<-[:VIEWER | EDITOR]-(a)
WITH TYPE(r) as relation, p, collect(distinct c) AS children
RETURN {name: p.name, Children: [c in children | {name: c.name, access:relation}]}
Here is my result:
And this is what I want to get:
My problem is that the result is split in two results, and nested child isn't nested in cohort.
An other thing that is tricky is that I don't want to get a node if I don't have a relation with it.
For example here I removed the relation between bob and cohort:
So I must not get cohort in my result, like this:
Here is my data if you want to try:
MERGE (project:Project:RootProject {name: "project test"})
MERGE (child1:Project {name: "cohort"})
MERGE (child2:Project {name: "protocol"})
MERGE (child3:Project {name: "experience"})
MERGE (child4:Project {name: "nested child"})
MERGE (project)-[:IS_PARENT]->(child1)
MERGE (project)-[:IS_PARENT]->(child2)
MERGE (project)-[:IS_PARENT]->(child3)
MERGE (child1)-[:IS_PARENT]->(child4)
MERGE (bob:Account {name: "bob"})
MERGE (bob)-[:EDITOR]->(child4)
MERGE (bob)-[:EDITOR]->(child2)
MERGE (bob)-[:VIEWER]->(child3)
MERGE (bob)-[:VIEWER]->(child1)
MERGE (bob)-[:VIEWER]->(project)
I have tried a lot of things but I never get a good result.
Here is my answer. The main thing is to construct the json object as parent then grandparent projects to the root project rather than mixing both (line 10). Notice that I removed getting VIEWER or EDITOR relationship since removing them is also faster.
//Get the root project from bob
MATCH (a:Account {name: "bob"})-[r]->(root:RootProject)
WITH a, root, {name:root.name, access:type(r)} as rootProject
//Get only those projects without nested parent
MATCH (a)-[r]->(p:Project) WHERE EXISTS((p)-[:IS_PARENT]-(root))
WITH a, rootProject, p, type(r) as relations
//Get those projects with another parent (or grand parent of root project)
OPTIONAL MATCH (p)-[:IS_PARENT]->(gp:Project)<-[r]-(a)
//Collect the children and grandchildren
WITH rootProject, collect({children: {name: p.name, access:relations}, grandchild:(case when gp is null then [] else [{name:gp.name, access:type(r)}] end)}) as allChildren
RETURN {name: rootProject.name, access: rootProject.access, children: [c in allChildren]} as projects
It may not meet your expectation, but how about this for scalability?
MATCH (a:Account {name: "bob"})-[r:VIEWER|EDITOR]->(c:Project)
MATCH path=(p)-[:IS_PARENT*]->(c)
WHERE (p)<-[:VIEWER | EDITOR]-(a)
WITH COLLECT(path) AS paths
CALL apoc.convert.toTree(paths, false, {
nodes: {Project: ['name']},
rels: {IS_PARENT: ['name']}
}) YIELD value
RETURN value

Neo4j (4.1.3) : How to create relationship on the fly when match returns exactly one row for one label

I am using neo4j 4.1.3. I have 2 labels : Person and Rec. I need to create a relationship between these 2 only when there exists exactly one person with that last name. When I run the below, relationships are created for all persons with that last name. My code is below:
MATCH (echk:REC {id: 'abcdef'})
OPTIONAL MATCH (p:Person) WHERE p.name CONTAINS 'Shirley'
WITH p, echk, count(p) as personcnt
where personcnt=1
CALL apoc.merge.relationship(p, 'Test', {}, {}, echk, {}) YIELD rel
RETURN p, echk, rel
I have 33 people with "Shirley' and 33 relationships are getting created with that REC. However, there should be no relationship.
Run this
MATCH (echk:REC {id: 'abcdef'})
OPTIONAL MATCH (p:Person) WHERE p.name CONTAINS 'Shirley'
RETURN p, echk, count(p) as personcnt
and you'll see why all 33 are created.
If there can only be one match, I suggest trying to find the "one" Shirley first, if they don't exist you are done, if more than one you are done. If and only if only one match returns, then continue on to match with 'abcdef' and do the merge.
If I have both p and count(p) in the WITH, I always get 1 for count(p) which makes sense. So I have changed my query as below to make this to work:
OPTIONAL MATCH (p:Person) WHERE p.name CONTAINS 'Shirley'
WITH count(p) AS cnt WHERE cnt = 1
MATCH (echk:REC {id: 'abcdef'})
MATCH (p:Person) where p.name contains 'Shirley'
CALL apoc.merge.relationship(p, 'Test', {}, {}, echk, {}) yield rel
I have now added this to my cypher logic to create dynamic relationship and it works as expected. Hopefully this helps someone at a later date.

Neo4J/Cypher Filter nodes based on multiple relationships

Using Neo4J and Cypher:
Given the diagram below, I want to be able to start at node 'A' and get all the children that have a 'ChildOf' relationship with 'A', but not an 'InactiveChildOf' relationship. So, in this example, I would get back A, C and G. Also, a node can get a new parent ('H' in the diagram) and if I ask for the children of 'H', I should get B, D and E.
I have tried
match (p:Item{name:'A'}) -[:ChildOf*]-(c:Item) where NOT (p)-[:InactiveChildOf]-(c) return p,c
however, that also returns D and E.
Also tried:
match (p:Item{name:'A'}) -[rels*]-(c:Item) where None (r in rels where type(r) = 'InactiveChildOf') return p,c
But that returns all.
Hopefully, this is easy for Neo4J and I am just missing something obvious. Appreciate the help!
Example data: MERGE (a:Item {name:'A'}) MERGE (b:Item {name:'B'}) MERGE (c:Item {name:'C'}) MERGE (d:Item {name:'D'}) MERGE (e:Item {name:'E'}) MERGE (f:Item {name:'F'}) MERGE (g:Item {name:'G'}) MERGE (h:Item {name:'H'}) MERGE (b)-[:ChildOf]->(a) MERGE (b)- [:InactiveChildOf] ->(a) MERGE (c)-[:ChildOf]->(a) MERGE (d)-[:ChildOf]->(b) MERGE (e)-[:ChildOf]->(b) MERGE (f)-[:ChildOf]->(c) MERGE (f)- [:InactiveChildOf] ->(c) MERGE (g)-[:ChildOf]->(c) MERGE (b)-[:ChildOf]->(h)
Note, I understand that I could simply put an "isActive" property on the ChildOf relationship or remove the relationship, but I am exploring options and trying to understand if this concept would work.
If a query interpreted as: find all the nodes, the path to which passes through the nodes unrelated by InactiveChildOf to the previous node, the request might be something like this:
match path = (p:Item{name:'A'})<-[:ChildOf*]-(c:Item)
with nodes(path) as nds
unwind range(0,size(nds)-2) as i
with nds,
nds[i] as i1,
nds[i+1] as i2
where not (i1)-[:InactiveChildOf]-(i2)
with nds,
count(i1) as test
where test = size(nds)-1
return head(nds),
last(nds)
Update: I think that this version is better (check that between two nodes there is no path that will contain at least one non-active type of relationship):
match path = (p:Item {name:'A'})<-[:ChildOf|InactiveChildOf*]-(c)
with p, c,
collect( filter( r in rels(path)
where type(r) = 'InactiveChildOf'
)
) as test
where all( t in test where size(t) = 0 )
return p, c
By reading and examining the graph, correct me if I'm wrong but the actual text representation of the cypher query should be
Find me nodes in a path to A, all nodes in that path cannot have an outgoing
InactiveChildOf relationship.
So, in Cypher it would be :
MATCH p=(i:Item {name:"A"})<-[:ChildOf*]-(x)
WHERE NONE( x IN nodes(p) WHERE (x)-[:InactiveChildOf]->() )
UNWIND nodes(p) AS n
RETURN distinct n
Which returns

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