在 Cypher 中创建并使用链表
在处理图时的某个时刻,您可能想要将一些节点组成一个链表。
如果每个要链接的节点都有自己的变量,这很容易,只需使用节点变量执行 CREATE 来创建模式。
// assume a, b, and c were previously matched and in scope
CREATE (a)-[:REL]->(b)-[:REL]->(c)
当然,您可以将更大的模式拆分为更小的模式,并在需要时对较小的模式使用 MERGE,以防该链式模式的某部分已经存在。
然而,当我们没有这些节点的单独变量时,例如所有要链接的节点都使用同一个变量或位于列表中时,如何链接它们并不明显。
使用 apoc.nodes.link() 将列表中的节点链接在一起
最简便的方法是利用 APOC Procedures 中的 apoc.nodes.link(),传入节点集合以及要使用的关系类型。将按给定类型在每对相邻节点之间创建出向关系。
MATCH (p:Person)
WITH p
ORDER BY p.name ASC
LIMIT 5
WITH collect(p) as persons
CALL apoc.nodes.link(persons, 'KNOWS')
RETURN persons
这将按名称取前 5 个 :Person 节点,并按给定顺序将它们链接成一个 5 节点的链。
在没有 APOC 的情况下链接节点
如果没有可用的 APOC,您可以仅使用 Cypher,但这需要一种棘手的三层 FOREACH 语法。
MATCH (p:Person)
WITH p
ORDER BY p.name ASC
LIMIT 5
WITH collect(p) as persons
FOREACH (i in range(0, size(persons) - 2) |
FOREACH (node1 in [persons[i]] |
FOREACH (node2 in [persons[i+1]] |
CREATE (node1)-[:KNOWS]->(node2))))
外层 FOREACH 用于仅遍历到倒数第二个节点,因为最后一个节点不需要外向关系。
另外两个 FOREACH 看起来更复杂,但它们仅是变通手段,用来为我们想要链接的每个节点提供变量,因为我们无法在 CREATE 中使用索引访问集合,例如:CREATE (persons[i])-[:KNOWS]→(persons[i+1]),也不能在 FOREACH 中使用 WITH 来生成可供使用的变量。如果再仔细看这两个 FOREACH,您会发现它们所做的仅仅是获取 persons 列表中位置 i 的节点和位置 i+1 的节点,并将它们分别映射为变量 node1 和 node2。
另外,我们可以使用 UNWIND 代替 FOREACH,从而为想要链接的节点起别名,但在更复杂且基数较高的查询中,这可能不是推荐的做法。
MATCH (p:Person)
WITH p
ORDER BY p.name ASC
LIMIT 5
WITH collect(p) as persons
UNWIND range(0, size(persons) - 2) as index
WITH persons[index] as node1, persons[index+1] as node2
CREATE (node1)-[:KNOWS]->(node2)
修改链表时的互斥
如果图中有链表,您可能在某个时刻需要对其进行修改,在链表的任意位置添加或删除节点。
如果列表修改查询可能并发执行,务必要使用适当的锁定机制,以确保更新时的互斥,避免竞争条件导致链表结构和正确性受损。
通常在链表头部放置一个始终存在的第 0 个节点,它本身不代表列表中的实际条目。任何需要修改列表的查询都可以首先对该第 0 节点加锁。
要对节点加锁,您可以在其上写入属性或标签,或使用 APOC 锁定过程。
例如,假设我们有一个关联到某个人的待办列表,并且想在该列表末尾追加。我们使用一个 :TODO_LIST 节点作为链接到该人的第 0 个节点。
MATCH (p:Person {name:'Keanu Reeves'})-[:TO_DO]->(listHead:TO_DO_LIST)
CALL apoc.lock.nodes([listHead])
MATCH (listHead)-[:NEXT*0..]->(end)
WHERE NOT (end)-[:NEXT]->()
CREATE (end)-[:NEXT]->(new:Event {name:'Punch Agent Smith'})
通过在第 0 个节点(listHead)上获取锁,在我们匹配列表本身之前,我们可以避免在查询并发执行时对列表进行修改导致的竞争条件(锁在事务提交时释放)。
例如,如果没有此锁定,在我们匹配到 end 节点后、在我们 CREATE 新事件之前,可能会有并发查询在末尾添加另一个事件,从而导致列表出现分支——原本的末尾节点现在拥有两个子节点,列表不再是单链表。
使用第 0 个节点进行加锁,无论列表是否为空,都能提供一个一致的锁定对象,并安全地避免并发更新导致的竞争条件。
此页面有帮助吗?