在 Cypher 中创建和使用链表
在使用图时,有时您可能希望从某些节点创建链表。
如果要链接的每个节点都有自己的变量,这很容易,只需使用节点变量对模式执行 CREATE 操作即可。
// assume a, b, and c were previously matched and in scope
CREATE (a)-[:REL]->(b)-[:REL]->(c)
当然,您可以将一个较大的模式分解为较小的模式,并在需要时对较小的模式使用 MERGE,当该链接模式的一部分可能已经存在时。
然而,当我们没有针对所讨论节点的独立变量时,例如所有要链接的节点都在同一个变量下或在一个列表中时,如何链接它们就不那么明显了。
使用 apoc.nodes.link() 将列表中的节点链接在一起
最简单的方法是利用 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 锁定存储过程。
举例来说,假设我们有一个与某人关联的待办事项列表,并且我们想向此列表追加内容。我们有一个 :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 个节点进行锁定,可以提供一个一致的锁定节点,无论列表是否为空,并提供一种安全的方法来避免并发更新引起的竞态条件。
此页面有帮助吗?