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
Related
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.
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
Initial Situation
Large Neo4j 3.4.6 graph with a tree-like structure (10 levels deep, 10 million nodes).
Unexceptional all nodes are connected with each other. The nodes as well as the relationships are in each case of the same type.
Exactly one central root node.
Reduced and simplified example:
Graphic representation
CREATE (Root:CustomType {name: 'Root'})
CREATE (NodeA:CustomType {name: 'NodeA'})
CREATE (NodeB:CustomType {name: 'NodeB'})
CREATE (NodeC:CustomType {name: 'NodeC'})
CREATE (NodeD:CustomType {name: 'NodeD'})
CREATE (NodeE:CustomType {name: 'NodeE'})
CREATE (NodeF:CustomType {name: 'NodeF'})
CREATE (NodeG:CustomType {name: 'NodeG'})
CREATE (NodeH:CustomType {name: 'NodeH'})
CREATE (NodeI:CustomType {name: 'NodeI'})
CREATE (NodeJ:CustomType {name: 'NodeJ'})
CREATE (NodeK:CustomType {name: 'NodeK'})
CREATE (NodeL:CustomType {name: 'NodeL'})
CREATE (NodeM:CustomType {name: 'NodeM'})
CREATE (NodeN:CustomType {name: 'NodeN'})
CREATE (NodeO:CustomType {name: 'NodeO'})
CREATE (NodeP:CustomType {name: 'NodeP'})
CREATE (NodeQ:CustomType {name: 'NodeQ'})
CREATE
(Root)-[:CONTAINS]->(NodeA),
(Root)-[:CONTAINS]->(NodeB),
(Root)-[:CONTAINS]->(NodeC),
(NodeA)-[:CONTAINS]->(NodeD),
(NodeA)-[:CONTAINS]->(NodeE),
(NodeA)-[:CONTAINS]->(NodeF),
(NodeE)-[:CONTAINS]->(NodeG),
(NodeE)-[:CONTAINS]->(NodeH),
(NodeF)-[:CONTAINS]->(NodeI),
(NodeF)-[:CONTAINS]->(NodeJ),
(NodeF)-[:CONTAINS]->(NodeK),
(NodeI)-[:CONTAINS]->(NodeL),
(NodeI)-[:CONTAINS]->(NodeM),
(NodeJ)-[:CONTAINS]->(NodeN),
(NodeK)-[:CONTAINS]->(NodeO),
(NodeK)-[:CONTAINS]->(NodeP),
(NodeM)-[:CONTAINS]->(NodeQ);
To be solved challenge
By means of a MATCH-WITH-UNWIND Cypher query I’m successfully able to select a subtree and bind it to a path. Let’s say the subtree spans over the nodes A,E,F,I and J.
Based on this path I need all leaves of the subtree, not the complete tree now.
.
MATCH
path = (:CustomType {name:'NodeA'})-[:CONTAINS*]->(:CustomType {name:'NodeJ'}) /* simplified */
WITH
nodes(path) as selectedPath
/* here: necessary magic to identify the leaf nodes of the subtree */
RETURN
leafNode;
Among other things I tried to solve the requirement with a WHERE NOT(node-->()) approach, but realized this works for leaves of the complete tree only. Unfortunately I was not able to convince the WHERE NOT(node-->()) clause to respect the selected subtree boundaries.
So, how can I find all leaves of a selected subgraph with Cypher and Neo4j? Can you please give me an advice how to solve this challenge? Many thanks in advance for pointing me into the right direction!
You correctly noted that the check node with no children is suitable only for the entire tree. So you need to go through all the relationships in the subtree, and find such a node of the subtree that is as the end of the relationship, but not as the start of the relationship:
MATCH
path = (:CustomType {name:'NodeA'})-[:CONTAINS*]->(:CustomType {name:'NodeJ'})
UNWIND relationShips(path) AS r
WITH collect(DISTINCT endNode(r)) AS endNodes,
collect(DISTINCT startNode(r)) AS startNodes
UNWIND endNodes AS leaf
WITH leaf WHERE NOT leaf IN startNodes
RETURN leaf
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.
I have 2 node tags: User, Tag.
Lets say that I have a user node that exists.
Is it possible to match that node,
and then if the tag exists merge between them,
and if the tag doesn't exist create the tag.
I tryed:
MATCH (n:User {name: "user"}) MERGE (n)-[r:follow]->(tag:Tag {name: "notexist")
In the above example it creates the node "notexist" and the relationship.
But if I have a node that is named "notexist" it doesn't merge, instead it creates another tag
named "notexist"
thank you
Lee,
Here's how to do this.
MATCH(n:User {name: 'user'})
WITH n
MERGE (t:Tag {name: 'notexist'})
WITH n, t
MERGE (n)-[r:follow]->(t);
Grace and peace,
Jim