扩展路径

扩展路径过程是 路径扩展器 中最基础的一个。该过程支持基于关系过滤器和节点过滤器进行路径遍历。如果需要对遍历进行更多控制,请参阅 带配置的扩展路径

过程概述

该过程描述如下

限定名称 类型

apoc.path.expand
apoc.path.expand(startNode ANY, relFilter STRING, labelFilter STRING, minDepth INTEGER, maxDepth INTEGER) - 返回从起始 NODE 扩展的 PATH 值,沿给定的 RELATIONSHIP 类型,从最小深度到最大深度。

过程

参数语法

此过程采用以下参数

  • start - 节点列表或节点 ID 列表

  • relationshipFilter - 要扩展的关系类型

  • labelFilter - 要扩展的节点标签

  • minLevel - 遍历中的最小跳数

  • maxLevel - 遍历中的最大跳数

关系过滤器

关系过滤器的语法描述如下

语法: [<]RELATIONSHIP_TYPE1[>]|[<]RELATIONSHIP_TYPE2[>]|…​

input type 方向

LIKES>

LIKES

OUTGOING (出站)

<FOLLOWS

FOLLOWS

INCOMING (入站)

KNOWS

KNOWS

BOTH (双向)

>

任意类型

OUTGOING (出站)

<

任意类型

INCOMING (入站)

节点标签过滤器

标签过滤器的语法描述如下

语法: [+-/>]LABEL1|LABEL2|*|…​

符号 过滤器类型 输入示例 描述

-

黑名单 (Denylist)

-Foe

路径中的任何节点都不会拥有存在于黑名单中的标签。

+

白名单 (Allowlist)

+Friend

路径中的所有节点必须拥有白名单中的标签(如果使用这些过滤器,则豁免终止和结束节点)。如果没有白名单运算符,则允许所有标签。

/

终止 (Termination)

/Friend

只返回到达具有给定标签节点的路径,并在该点停止进一步扩展。终止节点不需要遵守白名单。终止过滤优先于结束节点过滤。

>

结束节点 (End node)

>Friend

只返回到达具有给定标签节点的路径,但继续扩展以匹配其后的结束节点。结束节点不需要遵守白名单即可被返回,但只有在节点具有白名单中的标签时,才允许在其后进行扩展。

:

复合标签

Foe:Friend

这返回标签的合取,例如 /Foo:Bar 意味着终止节点必须同时匹配 FooBar。要包含没有特殊含义的 : 标签,请使用 \ 转义它们,例如 Foo\:Bar 是标签 Foo:Bar

示例

本节中的示例基于以下示例图

MERGE (mark:Person:DevRel {name: "Mark"})
MERGE (praveena:Person:Engineering {name: "Praveena"})
MERGE (joe:Person:Field {name: "Joe"})
MERGE (lju:Person:DevRel {name: "Lju"})
MERGE (zhen:Person:Engineering {name: "Zhen"})
MERGE (stefan:Person:Field {name: "Stefan"})
MERGE (alicia:Person:Product {name: "Alicia"})
MERGE (martin:Person:Engineering {name: "Martin"})
MERGE (jake:Person:Product {name: "Jake"})

MERGE (zhen)-[:KNOWS]-(stefan)
MERGE (zhen)-[:KNOWS]-(lju)
MERGE (zhen)-[:KNOWS]-(praveena)
MERGE (zhen)-[:KNOWS]-(martin)
MERGE (mark)-[:KNOWS]-(jake)
MERGE (alicia)-[:KNOWS]-(jake)

MERGE (alicia)-[:FOLLOWS]->(joe)
MERGE (joe)-[:FOLLOWS]->(mark)
MERGE (joe)-[:FOLLOWS]->(praveena)
MERGE (joe)-[:FOLLOWS]->(zhen)
MERGE (mark)-[:FOLLOWS]->(stefan)
MERGE (stefan)-[:FOLLOWS]->(joe)
MERGE (praveena)-[:FOLLOWS]->(joe)

下面的 Neo4j 浏览器可视化显示了示例图

apoc.path.expand

KNOWS 关系类型被认为是双向的,如果 Zhen 认识 Stefan,我们可以暗示 Stefan 也认识 Zhen。当使用 KNOWS 关系时,我们将忽略方向。

FOLLOWS 关系是有方向的,因此我们在使用它时会指定方向。

首先,我们从 Praveena 节点开始扩展路径。我们只想考虑 KNOWS 关系类型,因此将其指定为关系过滤器。

以下查询返回 Praveena KNOWS 的人员路径,跳数为 1 到 2 跳
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expand(p, "KNOWS", null, 1, 2)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 1. 结果
path 跳数

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})

1

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Martin"})

2

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:DevRel {name: "Lju"})

2

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Field {name: "Stefan"})

2

Praveena 只有与 Zhen 的直接 KNOWS 关系,但 Zhen 与其他 3 个人有 KNOWS 关系,这意味着他们距离 Praveena 有 2 跳远。

我们还可以提供节点标签过滤器来限制返回的节点。以下查询仅返回每个节点都具有 Engineering 标签的路径。

以下查询返回仅包含 Engineering 人员的路径,这些人是 Praveena KNOWS 的,跳数为 1 到 2 跳
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expand(p, "KNOWS", "+Engineering", 1, 2)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 2. 结果
path 跳数

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})

1

(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Martin"})

2

我们丢失了以 Lju 和 Stefan 结尾的路径,因为这些节点都没有 Engineering 标签。

我们可以指定多种关系类型。以下查询从 Alicia 节点开始,然后展开 FOLLOWSKNOWS 关系

以下查询返回包含 Alicia FOLLOWSKNOWS 的人员路径,跳数为 1 到 3 跳
MATCH (p:Person {name: "Alicia"})
CALL apoc.path.expand(p, "FOLLOWS>|KNOWS", "", 1, 3)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 3. 结果
path 跳数

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})

1

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Sales {name: "Jonny"})

1

(:Person:Product {name: "Alicia"})-[:KNOWS]→(:Person:Product {name: "Jake"})

1

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Sales {name: "Jonny"})-[:KNOWS]→(:Person:Sales {name: "Anthony"})

2

(:Person:Product {name: "Alicia"})-[:KNOWS]→(:Person:Product {name: "Jake"})←[:KNOWS]-(:Person:DevRel {name: "Mark"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:FOLLOWS]→(:Person:Product {name: "John"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Martin"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Praveena"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:DevRel {name: "Lju"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Field {name: "Stefan"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:KNOWS]→(:Person:Product {name: "Jake"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Sales {name: "Jonny"})-[:KNOWS]→(:Person:Sales {name: "Anthony"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})

3

(:Person:Product {name: "Alicia"})-[:KNOWS]→(:Person:Product {name: "Jake"})←[:KNOWS]-(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

此查询返回 19 条路径,Alicia 的人脉非常广!

我们还可以使用标签过滤器指定遍历终止标准。如果我们想在遍历遇到包含 Engineering 标签的节点时立即终止遍历,我们可以使用 /Engineering 节点过滤器。

以下查询返回包含 Alicia FOLLOWSKNOWS 的人员路径,跳数为 1 到 3 跳,并在到达带有 Engineering 标签的节点时立即终止
MATCH (p:Person {name: "Alicia"})
CALL apoc.path.expand(p, "FOLLOWS>|KNOWS", "/Engineering", 1, 3)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 4. 结果
path 跳数

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})

2

我们现在只剩下两条路径了。但此查询并没有捕获 Alicia 所有以带有 Engineering 标签的节点结尾的路径。我们可以使用 >Engineering 节点过滤器来定义一个遍历,它:

  • 仅返回在带有 Engineering 标签的节点处结束的路径

  • 并在那之后继续扩展到结束节点,寻找更多以 Engineering 标签结尾的路径

以下查询返回包含 Alicia FOLLOWSKNOWS 的人员路径,跳数为 1 到 3 跳,其中路径以带有 Engineering 标签的节点结尾
MATCH (p:Person {name: "Alicia"})
CALL apoc.path.expand(p, "FOLLOWS>|KNOWS", ">Engineering", 1, 3)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 5. 结果
path 跳数

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})

2

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Martin"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(:Person:Engineering {name: "Praveena"})

3

(:Person:Product {name: "Alicia"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Praveena"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})

3

我们的查询现在也返回了穿过 Praveena 和 Zhen 的路径,一条通往 Martin,另一条通往 Zhen 和 Praveena!