知识库

在 Cypher 中创建和使用链表

在使用图时,您可能希望从一些节点中创建一个链表。

如果要链接的每个节点都有自己的变量,这很容易,您只需使用节点变量创建模式即可

// assume a, b, and c were previously matched and in scope
CREATE (a)-[:REL]->(b)-[:REL]->(c)

当然,您可以将较大的模式分解成较小的模式,并在需要时对较小的模式使用 MERGE,当部分链接模式可能已经存在时。

但是,当我们没有要链接的节点的单独变量时,例如,如果所有要链接的节点都在同一个变量下,或在列表中,就不清楚如何链接它们。

最简单的方法是利用 APOC 过程中的 `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 锁定过程。

例如,假设我们有一个与人链接的待办事项列表,我们希望向此列表追加。我们有一个作为第 0 个节点的 :TODO_LIST 节点与人链接在一起。

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 个节点来锁定提供了一个一致的节点来锁定,无论列表是否为空,并提供了一个安全的方法来避免来自并发更新的竞争条件。