Dynamically Create Nodes & Relationships

Hi All,

I just started playing around with Neo4j and trying to learn Cypher. I've been stuck on getting a certain behavior and I'm sure there's probably a simple solution that I'm missing.

I've attached a screenshot of some sample data and I'll try my best to explain but I'm trying to accomplish.

First, the constraints...Every "Home" can only have one "Parent" and one "Parent" can have only 6 "Children".

So when the query is first ran, I'm given a "Parent" id. I need to query the data to see if the parent ID already exists, if it doesn't, I need to create a new Parent Node and a relationship to a "HOME" that doesn't have any Parents attached, but only to one of the Homes (not all of them).

Then when the data is queried again using the same "Parent" id, I need to create a "Child" Node and a relationship to the parent with the specific id. However, if the "Parent" already has 6 Children, I need to find a home with 0 Parents, create a new Parent to that home and also create a child to that parent.

This is the query I have worked out but doesn't do exactly what I'm trying to accomplish.

Match(h:Home)-[:IN]->(s:State) WHERE s.name = "California"
Optional Match(p:Parent) where p.id = "parent-uuid"
Optional Match(child:Child)-[l:LIVES_WITH]->(newParent:Parent)  WHERE p.id = "parent-uuid"
WITH p as p, count(l) as counts, h as h
LIMIT 1
WHERE counts < 6
CALL apoc.do.when(
   p IS NULL, 'CREATE (newParent:Parent {id: "parent-uuid"}), (newParent)-[:RUNS]->(h) RETURN h.id',
   'CREATE (child:Child {id: "child-uuid"}), (child)-[:LIVES_WITH]->(p) RETURN h.id',
  {p:p, counts:counts, h:h})
YIELD value
RETURN value

When I run the query the first time, it does create a new Parent node and attaches it to a home, and when I run it 6 more times, I do get 6 children attached to the parent.

However, after 6 children are attached to a parent, it doesn't try to find another home with 0 parents and then create child, parent, and relationships (child-[:LIVES_WITH]-parent-[:RUNS]-home).

Also, if I were to query for a different parent uuid, the query will attach that new parent to a home with an already existing parent, which I don't want. It should go to a home with 0 parents.

I hope that makes sense and I'm happy to clarify. Any help would be greatly appreciated, thank you!

Cypher is not a scripting language. You figured out how to add conditional logic using the apoc do.when procedure. You can also perform conditional logic using call subqueries, but you can't return anything because it will terminate the query when one of the subqueries terminates. You also have to use a 'double with' to import the parameters, as a 'where' clause is not allowed after the first 'with' in a subquery.

Anyways, I refactored your query to use call subqueries, so I could add a third one to address the cause when the number of children is greater than six. I tested the first two subqueries. They behaved as your query does. I did not test the remember scenario. Give it a try.

Match(h:Home)-[:IN]->(s:State) 
WHERE s.name = "California"
with h, s
limit 1
Optional Match(p:Parent) where p.id = "parent-uuid"
Optional Match(child:Child)-[l:LIVES_WITH]->(p)
WITH s, p, h, count(p) as noOfChildren
call {
    with p, h
    with p, h
    where p is null
    CREATE (newParent:Parent {id: "parent-uuid"}), (newParent)-[:RUNS]->(h)
}
call {
    with p, noOfChildren
    with p, noOfChildren
    where p is not null and noOfChildren < 6
    CREATE (child:Child {id: "child-uuid"}), (child)-[:LIVES_WITH]->(p)
}
call {
    with s, noOfChildren
    with s, noOfChildren
    where noOfChildren >= 6
    Match(h:Home)-[:IN]->(s) 
    Where not exists ((:Parent)-[:RUNS]->(h))
    with h
    limit 1
    create (p:Parent)-[:RUNS]->(h)
    create (child:Child)-[:LIVES_WITH]->(p)
}

Hey Gary,

Really appreciate your help. I made a slight tweak (not sure if it's "right" from a performance perspective) but it looks like it covers all my scenarios.

Match(h:Home)-[:IN]->(s:State)
WHERE s.name = "California" 
with h, s
limit 1
Optional Match(p:Parent) where p.id = "p3"
Optional Match(child:Child)-[l:LIVES_WITH]->(p)
WITH s, p, h, count(p) as noOfChildren
call {
    with p, h
    with p, h
    where p is null
    Match(home:Home)-[:IN]->(s) 
    Where not exists ((:Parent)-[:RUNS]->(home))
    with home
    Limit 1
    CREATE (newParent:Parent {id: "p3"}), (newParent)-[:RUNS]->(home)
}
call {
    with p, noOfChildren
    with p, noOfChildren
    where p is not null and noOfChildren < 6
    CREATE (child:Child {id: "child-uuid"}), (child)-[:LIVES_WITH]->(p)
}
call {
    with s, noOfChildren
    with s, noOfChildren
    where noOfChildren >= 6
    Match(h:Home)-[:IN]->(s) 
    Where not exists ((:Parent)-[:RUNS]->(h))
    with h
    limit 1
    create (p:Parent)-[:RUNS]->(h)
    create (child:Child)-[:LIVES_WITH]->(p)
}

I did want to follow up on your comment "you can't return anything because it will terminate the query when one of the subqueries terminates". I can't return anything at any point in that query? Not from any of the calls but from the "outer" query?

You can from the outer query. You can’t from the subqueries. The reason is if at least one doesn’t have a return value, then the entire query terminates for that row. Since the subqueries are designed to be mutually exclusive, there will always be two that don’t return something because their where clause will be false, thus the query will terminate.

You are allowed to return values in a subquery. The return values will be appended to the outer query results. This is why the query will terminate if the subquery has no result.

Got it, makes sense. Thanks again for your help, I appreciate it.

1 Like