Learn Why You Should Understand “Moving Relationships,” a New Kind of Data Relationship in Neo4j

[As community content, this post reflects the views and opinions of the particular author and does not necessarily reflect the official stance of Neo4j.]

Introduction


A graph database is not only a simple way to store connected data, it is also a powerful tool to manage dynamic relationships between data.

Since relationships are natively implemented in Neo4j, we can use them as a simple way to identify a group of connected nodes. But it is not the only usage we can make of them.

There are many use cases where consecutive queries can move the relationships between nodes. These relationships are intended to store a particular state for a sub graph like a pointer for example. Let’s have a closer look.

Relationship as a Pointer


To observe this behavior, our first example is the famous Linked List. A linked list is a list where each item is ordered according to its relative position compared to the others items.

On each call, the currently pointed element is returned and then the pointer moves to the next element (like in Java with the Iterator object iterator ().next () ).

First we need to create a :LIST node with three :ELEMENT nodes linked to it.

MERGE (elem1:ELEMENT{name:"elem1"})-[:IS_MEMBER_OF]->(list:LIST{name:"List"})
MERGE (elem2:ELEMENT{name:"elem2"})-[:IS_MEMBER_OF]->(list)
MERGE (elem3:ELEMENT{name:"elem3"})-[:IS_MEMBER_OF]->(list)

Fig1. The list with elements

Figure 1. The list with elements

Then we need to link these elements in a relative ordered way. (The initial position is not necessarily important. We chose here to match elements alphabetically ordered by their name property) :

    MERGE (elem:ELEMENT)-[r:IS_MEMBER_OF]->(list:LIST)
    WITH elem ORDER BY elem.name ASC
    WITH COLLECT(elem) AS elems
    FOREACH (n IN RANGE(0, LENGTH(elems)-2) |
    FOREACH (prec IN [elems[n]] |
    FOREACH (next IN [elems[n+1]] |
    MERGE prec-[:NEXT]->next)))

Fig2. Linked elements

Figure 2. Linked elements

Here we use a loop (the first FOREACH clause) to browse the collection of elements and to create a relationship between the previous node and the next node.

If you need more explanations about this query, you can find a very interesting post from Mark Needham here: Neo4j: Cypher – Creating relationships between a collection of nodes.

The next step consists in placing a pointer at the initial position, meaning on the header element of this list (the element without a :NEXT incoming relationship):

    MATCH (list:LIST)
    MATCH (elem:ELEMENT) WHERE NOT elem<-[:NEXT]-()
    MERGE list-[:POINTER]->elem 

Fig3. Initial position of the pointer

Figure 3. Initial position of the pointer

Now, we have to write a typical query which returns the current pointed element and emulates the pointer movement.

Returning the current element is simple :

    MATCH (list:LIST)-[:POINTER]->(current:ELEMENT) 
    RETURN current

But emulating the pointer movement requires three Cypher operations :

    1. Finding the next node
    2. Deleting the current pointer relationship
    3. Creating a new pointer relationship on the next node
The final query is:

    MATCH (list:LIST)<-[:IS_MEMBER_OF]-(elems:ELEMENT)
    MATCH (list)-[p:POINTER]->(current:ELEMENT) 
    MATCH current-[:NEXT]->(next:ELEMENT) 
    DELETE p 
    MERGE list-[pnew:POINTER]->next
    RETURN DISTINCT list,current,next,pnew,elems

Fig4. First move of the pointer

Figure 4. First move of the pointer

The pointer moved!

If we take five minutes to have a closer look at the type of the relationships, we have:

    • :IS_MEMBER_OF to determine what the elements of this list are
    • :NEXT to determine what the next element to the current one is
    • :POINTER to determine what the current element is
As we can see, the nature of each relationship type is different. The first two ones have a long lifecycle (as long as the list exists) and the last one has another function: its lifetime may be short because it is only a volatile state at any given moment.

This is a moving relationship!

Relationships as a Three-Way Switch


Yes, it is a weird title and a strange use case. I think that it is interesting to introduce another way of thinking about data relationships.

Previously we were talking about a relationship as a pointer, now we have to consider combined states. And the best example to introduce this notion is the electrical three-way switch.

In the hallway of your house, you need to switch the light on or off from more than one location. You have to be able to switch the light on from the entrance (front switch) and to switch it off from the other end (rear switch), since it wouldn’t make sense to turn the light on only from the last place where you turned it off.

In the domain of electricity such a switch is called a three-way switch, and its implementation is as follows:

Fig5. Three-way switch

Figure 5. A three-way switch. Image source.

Very well, but what is the link with graphs?

In the domain of electricity, switching the light on requires a closed circuit. In graph theory, a closed circuit is called a cycle, meaning that it should be possible to easily convert it into a graph.

If we can detect a cycle through these elements we can deduct that the lamp is on, whereas if we cannot find a closed cycle, the light remains off.

First, we need to represent this schema in Neo4j, and we consider that the initial position is when the light is off:

    CREATE 
    (generator:GENERATOR {name:"generator"}),
    (lamp:LAMP {name:"lamp"}),
    (s1:SWITCH {name:"front-switch"}),
    (e11:ENDPOINT {name:"front-endpoint-1"}),
    (e12:ENDPOINT {name:"front-endpoint-2"}),
    (s2:SWITCH {name:"rear-switch"}),
    (e21:ENDPOINT {name:"rear-endpoint-1"}),
    (e22:ENDPOINT {name:"rear-endpoint-2"}),
    generator-[:TO]->lamp,
    lamp-[:TO]->s2,
    s2-[:POINTER]->e22,
    e22-[:TO]->e12,
    generator<-[:TO]-s1,
    e11-[:POINTER]->s1,
    e11<-[:TO]-e21

Fig6. First adaptation

Figure 6. The first adaptation.

The TO relationships represent the electrical wires.
The POINTER relationships represent the current states of the switches.

But this graph isn’t sufficient, because we can’t find the next Endpoint from the currently pointed position. We need to build a circular linked list around the endpoints for each switch.

Here is the query for the rear switch:

    MATCH (s2:SWITCH {name:"rear-switch"})
    MATCH (endpoint:ENDPOINT) WHERE endpoint.name=~"rear.*"
    MERGE endpoint-[:IS_A_PART_OF]->s2
    WITH endpoint ORDER BY endpoint.name ASC
    WITH COLLECT(endpoint) AS elems
    FOREACH (n IN RANGE(0, LENGTH(elems)-2) |
    FOREACH (prec IN [elems[n]] |
    FOREACH (next IN [elems[n+1]] |
    MERGE prec-[:NEXT]->next)))
    WITH elems[0] as beginning, elems[LENGTH(elems)-1] as ending
    MERGE beginning<-[:NEXT]-ending

And here is the query for the front switch:

    MATCH (s1:SWITCH {name:"front-switch"})
    MATCH (endpoint:ENDPOINT) WHERE endpoint.name=~"front.*"
    MERGE endpoint-[:IS_A_PART_OF]->s1
    WITH endpoint ORDER BY endpoint.name ASC
    WITH COLLECT(endpoint) AS elems
    FOREACH (n IN RANGE(0, LENGTH(elems)-2) |
    FOREACH (prec IN [elems[n]] |
    FOREACH (next IN [elems[n+1]] |
    MERGE prec-[:NEXT]->next)))
    WITH elems[0] as beginning, elems[LENGTH(elems)-1] as ending
    MERGE beginning<-[:NEXT]-ending

Note that the last query line MERGE beginning<-[:NEXT]-ending is useful to link the last element to the first and thus to obtain a circular reference.

Fig7. Second adaptation

Figure 7. The second adaptation.

Lamp test query:

    MATCH (lamp:LAMP)-[r:TO|POINTER*]->(g:GENERATOR) RETURN lamp

No result here (the lamp is off), meaning the path is broken since a TO or POINTER relationship is missing:

Fig8. Broken path

Figure 8. A broken path.

When a user toggles the rear switch to turn on the light, it triggers the query below.

Toggling the rear switch query:

    MATCH (s2:SWITCH{name:'rear-switch'})-[p:POINTER]->(current:ENDPOINT) 
    MATCH current-[:NEXT]->(next:ENDPOINT) 
    DELETE p 
    MERGE s2-[pnew:POINTER]->next
    RETURN s2,current,next,pnew

Fig9. Touching the rear switch

Figure 9. Touching the rear switch.

The rear switch pointer moves from its initial position to the second one and thus closes the path between the lamp and the generator.

Now let us test the lamp with this query:

    MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR)-[r2:TO]-(lamp) RETURN DISTINCT lamp

The light is on!

Fig10. A closed path

Figure 10. A closed path.

The result would be the same with the front switch, since we emulated a three-way switch.

It is a good starting point for home automation applications, isn't it? Imagine a graph that stores all the positions of lamps, doors, gates or shutters of your house...and then, more complex graphs to manage an entire building (i.e., facility management)!

You can also try to obtain the same results in SQL, but I expect this to be challenging.

Note that our representation is simplified on purpose. A switch could be modeled using a pre-defined component with three endpoints (one input and two outputs). If so, we could model a graph composed of combinations of components (with internal states) rather than composed of combinations of unitary nodes, but that is another story.

Conclusion


As we can see, there are different natures of data relationships: Some of them have a permanent life, like the electrical wires in our previous example; some of them, like pointers, have a lifecycle and are supposed to store a state. The latter are the ones I call moving relationships.


Special thanks to Patricia for reviewing.

Editor's note: Sylvain Roussy is also the author of the French ebook introduction to graph databases and Neo4j, and he'll be publishing a new edition in December with an update for Neo4j 2.3.


Want to learn more about graph databases? Click below to get your free copy of O’Reilly’s Graph Databases ebook and discover how to use graph technologies for your mission-critical application today.

Download My Free Copy

 

Keywords:  


About the Author

Sylvain Roussy , Co-organizer, Neo4j Meetup in Lyon

Sylvain Roussy Image

Sylvain Roussy is the co-organizer of the Neo4j Meetup group in Lyon, France, and the author of Neo4j Des données et des graphes – Prise en main, the French ebook introduction to Neo4j and graph databases.

Sylvain is also a contributor to the Neo4j blog.


7 Comments

Anish says:

I had two doubts in queries written above..

a) for multiple MATCH statements you have not used WITH in between them, is that no longer necessary in future versions or u have just omitted it for ease of writing here?
e.g don’t we need
MATCH (list:LIST)(current:ELEMENT) with list, elems, current
MATCH current-[:NEXT]->(next:ELEMENT)

b) For lamp check query, I don’t think loop has been completed the way you have written.
MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR) RETURN DISTINCT lamp will return a result in all case since lamp(g:GENERATOR) RETURN DISTINCT lamp
or
MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR)-[r:TO|POINTER*]-(lamp:LAMP) RETURN DISTINCT lamp
to check for completeness of circle?

or am I missing something here??

Anish says:

Above comment seem to have been messed up by whatever auto checker or editor this site seem to have.

b) For lamp check query, I don’t think loop has been completed the way you have written.
MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR) RETURN DISTINCT lamp will return a result in all case since lamp (g:GENERATOR) RETURN DISTINCT lamp
or
MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR)-[r:TO|POINTER*]-(lamp:LAMP) RETURN DISTINCT lamp
to check for completeness of circle?

Sylvain Roussy says:

Hi all,

Thanks for your comments.

a) Yes, it was for ease of writing that I wrote the queries this way, which can indeed potentially lead to Cartesian products. I wanted to demonstrate a concept without regard to performances at this stage (no indexes too).

b) You are right but in this example I consider that the direct relationship between the Generator node and the Lamp node always exists (to simplify).
However, there is an error in my case, the lamp test query should be oriented :
MATCH (lamp:LAMP)-[r:TO|POINTER*]->(g:GENERATOR) RETURN lamp

In complete circle case the query is :
MATCH (lamp:LAMP)-[r:TO|POINTER*]-(g:GENERATOR)-[r2:TO]-(lamp) RETURN distinct lamp

Rony says:

Hi, thanks for a great blog. However, I have a query.

MERGE prec-[:TO]->next)))

If I want to add properties to relationship [:TO] i.e. [:TO { id: n.property3, place: n.property4 , ..}]
i.e. from a .csv file. How can it be achieved?

My code:

LOAD CSV WITH HEADERS FROM “file:///C:/Users/..” AS n
WITH distinct n.property1 as property1, n.property2 as property2
MERGE (user:USER{us1:property1})
WITH user ORDER BY user.us1 ASC
WITH COLLECT (user) AS temps
FOREACH (i IN RANGE (0, LENGTH(temps) -2)|
FOREACH (prec IN [temps[i]] |
FOREACH (next IN [temps[i+1]] |
MERGE prec -[to:TO {id: n.property3, place: n.property4 , ..}]-> (next )))

I need to read and set properties for [:TO] relationship using the .csv file

Bill says:

In closing, you state: “A switch could be modeled using a pre-defined component with three endpoints (one input and two outputs).” As opposed to the solution that you posted in the article.

Could you please post an example of what that would look like?

Hi,

You can imagine something like that (I can’t post here images) :

CREATE (switch:Switch{name:”hall-swicth”}),
(input:EndPoint:Input),
(out1:EndPoint:Output{name:”out-1″}),
(out2:EndPoint:Output{name:”out-2″})
CREATE (input)-[:TO]->(switch)
CREATE (swicth)-[:TO]->(out1)
CREATE (swicth)-[:TO]->(out2)
// And relationships with pointer

And use this query each time you place a switch on a electrical plan (via graphical UI for exemple).

Then, you can also imagine other type of pre-defined queries for components and thinking your graph at high level (Hypergraph ?). Like Tactical compared to Strategy are.

I hope this response brings you some information,

Sylvain

Nikolay says:

Normally relationships may have properties. How to move a relationship with properties from one node to another node keeping the properties? I cannot see a way to do this in one step (one cypher query).

Leave a Reply

Your email address will not be published. Required fields are marked *

Subscribe

Upcoming Event

 


Have a Graph Question?

Stack Overflow
Community Forums
Contact Us

Share your Graph Story?

Email us: content@neo4j.com