16
votes

I have been using cypher and neo4j for a while now, and I keep running into a similar problem again and again.

I would like to be able to add a "temporary" property to a node thats exists only in the returned data and is not persisted to the Neo4j database.

For example the current problem I am working on is given an N-ary tree structure in the graph database, I would like to return the children of a specific node, and an informational boolean letting me know weather those children have children.

The query to get this information is simple enough:

MATCH (parent:Item { guid: "Identifier here" })-[:HASCHILD]->(child:Item)
OPTIONAL MATCH (child)-[:HASCHILD]->(grandchild:Item)
WITH parent, child, LENGTH(COLLECT(grandchild)) > 0 AS has_children

Now I have the parent, child and a boolean letting me know weather there are further children.

What I would like to be able to do next is set the has_children boolean as a property on the child nodes, but just for returning the query, I don't need the property persisted.

I know there is are some solutions to get what I am looking for but none of them are specifically what I am looking for.

1) I could create a temporary node: (Continued from above query)

...
WITH parent, child, LENGTH(COLLECT(grandchild)) > 0 AS haschildren,
{
    childkey1: child.childkey1,
    childkey2: child.childkey2,
    ...
    has_children: haschildren
} AS tempnode
RETURN parent, tempnode

This solution is not great, you have to know the exact node type and all the properties of the node you are dealing with, it will not work in the general case.

2) I could SET and REMOVE the boolean, temporarily persisting it to the database (as described in Neo4J create temp variable within Cypher).

This is not great as it will cause multiple db writes (for add and remove). Also, it is not as dynamic as option 1 as it would only be able to handle simple types. (Option 1 can handle more complex cases, like adding a collection of nodes instead of a simple boolean).

As far as I know, these are the only options when it comes to temporary return data. I'm wondering am I missing any thing? Is it possible to make this more simple or dynamic?

Something along the lines of

 WITH parent, child, LENGTH(COLLECT(grandchild)) > 0 AS haschildren,
 TEMPSET child.has_children = haschildren

Please note that I have simplified the examples given a little. I have run into this issue multiple times in different contexts in neo4j. Most times I either go with option 1, or build the correct structure by post-processing on the application side once the query returns.

Is there a better way?

2
Not sure it has all all your requirements, but I do see you didn't mention neo4j-contrib.github.io/neo4j-apoc-procedures/… so maybe those fit the bill ?Tom Geudens
I had actually been reading up on these docs earlier on before posting this question. They look promising to create temporary nodes/patterns, but we are still faced with the same issue as the first issue in my question, having to know all the properties of the original node. As far as I can see you cant use these functions to arbitrarily add a property to the virtual node, all the virtual node properties must be named...Dalaigh88

2 Answers

29
votes

You can try returning a map projection. Take a look in this example:

// Creating a sample node    
CREATE (:Person {name:'Jon', id: 1})

Returning the projection with a extra boolean:

MATCH (p:Person {id:1})
WITH p, true AS has_children
RETURN p{.*, has_children:has_children}

The result:

╒═════════════════════════════════════════╕
│"p"                                      │
╞═════════════════════════════════════════╡
│{"id":1,"name":"Jon","has_children":true}│
└─────────────────────────────────────────┘

The above query is returning all properties of p node with an extra boolean.

0
votes

Virtual nodes, provided by the APOC library, might be useful for this as well, particularly if you need to preserve (or otherwise map) relationships of the node you're adding a property to.

See https://neo4j.com/docs/labs/apoc/current/virtual/ and also https://community.neo4j.com/t/virtual-nodes-and-relationships-use-case/110. See https://neo4j.com/docs/labs/apoc/current/introduction/ for installation etc of Apoc.

Essentially you create temporary (virtual) copies of the node(s) you want to add properties to, and also temporary (virtual) relations connecting these to existing or other virtual nodes.

E.g. to in a user->team relationship, we could add a new property to users and a count to relations like this (tested with Neo4j v3.5):

MATCH (user:User)-[r:MEMBER_OF]-(team:Team)
WITH user, r, team, count(*) AS count
CALL apoc.create.vNode(labels(user), user{.*, something:'new'})
    YIELD node AS vUser
CALL apoc.create.vRelationship(vUser, type(r), r{.*,count}, team)
    YIELD rel AS vr
RETURN vUser, team, vr

Or for your example, something like this (untested):

MATCH (parent:Item { guid: "Identifier here" })-[r:HASCHILD]->(child:Item)
OPTIONAL MATCH (child)-[:HASCHILD]->(grandchild:Item)
WITH parent, child, LENGTH(COLLECT(grandchild)) > 0 AS has_children
CALL apoc.create.vNode(labels(child), child{.*, has_children:has_children})
    YIELD node AS vChild
CALL apoc.create.vRelationship(parent, type(r), r{.*}, vChild)
    YIELD rel AS vr
RETURN parent, vChild, vr

NOTE that the Node IDs returned by the apoc.create.vNode are arbitrary: they are negative, decrementing from -1, and do NOT correspond to the underlying node ID ()