网络版本
最近,Ian Robinson发表了一篇关于网络版本控制的精彩博客(在此阅读),内容涉及使用身份节点以及将结构与状态分离。在这个Gist中,我想通过介绍另一种使用relationnodes的方法来发表我的看法。这更多是关于网络结构,而非节点状态,但它以不同的方式处理版本控制。正如Ian在他的博客中所说,版本控制总是需要权衡取舍,因为它会增加存储和处理的额外负担。
Relationnodes?
Relationnodes是什么?
关系节点(Relation nodes)是位于两个网络节点之间的中间节点,表示节点之间的关系。
因此,如果A和B是两个网络节点,与其用关系[:RELTYPE]
连接它们,不如在它们之间插入一个关系节点(relationnode),并用[:RS]
和[RE]
连接,它们分别代表关系开始(relation start)和关系结束(relation end)。

为什么使用relationnodes?
Neo4j(我偏好的图数据库)当前2.0.3版本不允许在关系和节点之间创建关系。如果我想表示特定网络节点之间的特定关系属于特定网络(该网络本身由一个节点表示),我就需要这样做。
此外,我的用例要求我在不同网络之间共享网络节点,但以不同的方式组合。
另一个原因是网络由节点之间的关系决定,而非节点本身。如果我有两个节点A和B,这并不能告诉我它们的关系。无论是方向、类型,还是连接它们的多个关系,都无法得知。另一方面,如果我知道一个关系,我就知道它的起始/结束节点及其类型。节点可以独立存在,而关系只能在有起始和结束节点的情况下存在。
使用这种方法的第四个原因是它允许我比较不同的网络。例如,回答“网络1的第3版可以与网络2的第1版共存吗?”这样的问题。然而,这不会是本次Gist的主题,因为它需要额外的思考时间。
创建网络
设置
创建网络的过程包括以下步骤
-
创建表示网络的节点
-
创建网络节点
-
创建通过
[:RS]
和[:RE]
关系连接到网络节点,并通过[:UPDATE {type:'ADD', version:1}]
连接到网络节点的关系节点(relationnodes)。
通过设置查询创建了网络1的第一个版本,将A连接到B再连接到C。请注意,[:UPDATE]
关系的version
属性可以简单地替换为或附带一个时间戳属性,使图具有时间感知能力。
CREATE (_0:`nw` {`name`:"Network1"})
CREATE (_5:`nwnode` {`name`:"A"})
CREATE (_6:`nwnode` {`name`:"B"})
CREATE (_7:`nwnode` {`name`:"C"})
CREATE (_13:`relationnode` {`id`:1})
CREATE (_14:`relationnode` {`id`:2})
CREATE _5-[:`RS`]->_13
CREATE _6-[:`RS`]->_14
CREATE _13-[:`RE`]->_6
CREATE _14-[:`RE`]->_7
CREATE _0-[:`UPDATE` {`type`:"ADD", `version`:1}]->_14
CREATE _0-[:`UPDATE` {`type`:"ADD", `version`:1}]->_13
此网络中的关系是
MATCH (s:nwnode)-[:RS]->(rn:relationnode)-[:RE]->(e:nwnode)
RETURN s.name as From,e.name as To ORDER BY From,To
向网络添加节点
如果需要向网络添加一个节点,我们需要
-
创建新节点
-
创建一个关系节点(relationnode)
-
这次创建一个
[:UPDATE]
关系,版本为2
MATCH (nw:nw {name:"Network1"}),(sn:nwnode {name:"C"})
CREATE (_8:`nwnode` {`name`:"D"})
CREATE (_15:`relationnode` {`id`:3})
CREATE (sn)-[:`RS`]->_15-[:`RE`]->_8
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->_15
网络版本2中的关系是
MATCH (s:nwnode)-[:RS]->(rn:relationnode)-[:RE]->(e:nwnode)
RETURN s.name as From,e.name as To ORDER BY From,To
从网络中移除节点
如果需要从网络中移除节点,我们所做的是向相关的关系节点(relationnodes)添加一个[:UPDATE {type:'REMOVE', version:3}]
关系。因此,从网络1中完全移除节点“C”是一个涉及以下步骤的问题:
-
找到将“C”与网络1连接的关系节点(relationnodes)
-
在网络1和这些关系节点(relationnodes)之间创建
[:UPDATE {type:'REMOVE', version:3}]
关系
MATCH (nw:nw {name:"Network1"})-[:UPDATE]->(rn:relationnode)-[:RS|RE]-(n:nwnode {name:"C"})
WITH nw,COLLECT(DISTINCT rn) AS rns
FOREACH ( i IN rns |
CREATE (nw)-[:`UPDATE` {`type`:"REMOVE", `version`:3}]->(i)
)
现在存在的完整关系列表由这个查询给出
MATCH (nw:nw {name:"Network1"})-[u:UPDATE]->(rn:relationnode),(s:nwnode)-[:RS]->(rn)-[:RE]->(e:nwnode)
RETURN u.version AS Version,u.type AS UpdateType,s.name AS From, e.name AS To
ORDER BY Version,UpdateType,From
为了获取网络版本3中的关系,我们必须将搜索范围限制在网络内,并且只考虑那些最近的[:UPDATE]
类型为“ADD”的事务。
你会看到,通过移除C,我们也把D从网络中切断了。这很可惜,但幸运的是我们也可以在现有节点之间创建关系。
在两个现有节点之间创建链接
如果需要向网络添加一个节点,我们需要
-
获取节点
-
创建一个关系节点(relationnode)
-
这次创建一个
[:UPDATE]
关系,版本为4
MATCH (nw:nw {name:"Network1"}),(sn:nwnode {name:"B"}),(en:nwnode {name:"D"})
CREATE (rn:`relationnode` {`id`:7})
CREATE (sn)-[:`RS`]->(rn)-[:`RE`]->(en)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:4}]->(rn)
网络的更新版本是
历史版本
只只需考虑直到该版本的[:UPDATE]
关系,即可检索到网络的特定版本。
网络1的版本1
并以图的形式呈现(目前仅在此链接有效 14MAY2014 https://jexp.github.io/graphgist/?40364ac2a52f57aa520a 。感谢@Jim_Salmons指出)
添加第二个网络
采用这种方法的原因之一是,我需要在第二个网络中重用这些网络节点,且该网络有自己的关系。
让我们创建一个有两个版本的网络2。
-
版本1
-
C到A
-
-
版本2
-
A到D
-
B到A
-
D到E
-
E到A
-
E到B
-
MATCH (na:nwnode {name:"A"}),(nb:nwnode {name:"B"}),(nc:nwnode {name:"C"}),(nd:nwnode {name:"D"})
CREATE (nw:`nw` {`name`:"Network2"})
CREATE (ne:`nwnode` {`name`:"E"})
CREATE (nc)-[:`RS`]->(rn1:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:1}]->(rn1)
CREATE (nw)-[:`UPDATE` {`type`:"REMOVE", `version`:2}]->(rn1)
CREATE (nb)-[:`RS`]->(rn2:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn2)
CREATE (na)-[:`RS`]->(rn3:relationnode)-[:`RE`]->(nd)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn3)
CREATE (nd)-[:`RS`]->(rn4:relationnode)-[:`RE`]->(ne)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn4)
CREATE (ne)-[:`RS`]->(rn5:relationnode)-[:`RE`]->(nb)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn5)
CREATE (ne)-[:`RS`]->(rn6:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn6)
之后,当前网络2的关系是
后记
这是我的第一个Gist。设置起来很有趣。要是能有可视化查询结果而非整个图的//graph之类的东西就好了。更新:我们已经在https://jexp.github.io/graphgist/?40364ac2a52f57aa520a上有了 :) 我想在不久的将来也会在https://gist.neo4j.org/上提供。
我倾向于尽可能地以图的方式来处理事物。不确定这次是否成功了,但我努力记住,打开和关闭节点与关系而非进行遍历会带来成本。
向[:UPDATE]
关系添加另一个属性{status: "pending"}
,将允许我运行Cypher查询来回答诸如“如果我这样做会怎样?”的问题。
正如我之前所说,向[:UPDATE]
关系添加时间戳将使其具有时间感知能力。你也可以创建一个版本节点的链表,并将[:UPDATE]
链接到这些节点。
比较不同的网络将是我下一个Gist的主题。
我还将探讨使用关系节点(relationnodes)时的可视化方面。有时你想看到它们,有时它们会碍事,因为力导向布局可能会在你不想要的地方产生角度。幸运的是,Cypher允许你以可视化引擎可以处理的方式调整结果,并“欺骗”可视化引擎,使其认为你提供给它的是一个关系。
感兴趣?在@tomzeppenfeldt / @ophileon关注我。任何评论当然也欢迎。
此页面有帮助吗?