使用配置扩展路径

使用配置扩展路径过程支持强大的变长路径遍历,并提供对遍历的精细控制。对于不需要精细控制遍历的更基本算法版本,请参阅扩展路径

过程概述

该过程描述如下

限定名称 类型

apoc.path.expandConfig
apoc.path.expandConfig(startNode ANY, config MAP<STRING, ANY>) - 从起始节点扩展给定关系类型,返回从最小深度到最大深度的路径值。

过程

配置参数

这些过程支持以下配置参数

表 1. 配置参数
名称 类型 默认 描述

minLevel

INTEGER

-1

遍历中的最小跳数

maxLevel

INTEGER

-1

遍历中的最大跳数

relationshipFilter

STRING

null

要遍历的关系类型和方向。

参见关系过滤器

labelFilter

STRING

null

要遍历的节点标签。

参见标签过滤器

sequence

STRING

null

逗号分隔的交替标签和关系过滤器,用于重复序列中的每个步骤。如果存在,将忽略labelFilterrelationshipFilter,因为它具有更高的优先级。

参见指定节点标签和关系类型的序列

beginSequenceAtStart

BOOLEAN

true

从起始节点开始,匹配距离起始节点一个节点的节点标签和/或关系类型序列(在relationshipFilterlabelFiltersequences中定义)。

uniqueness

STRING

RELATIONSHIP_PATH

在遍历中扩展关系时使用的策略。

参见唯一性

bfs

BOOLEAN

true

遍历时使用广度优先搜索。如果设置为false则使用深度优先搜索。

filterStartNode

BOOLEAN

false

labelFiltersequence是否适用于扩展的起始节点。

limit

INTEGER

-1

限制返回的路径数量。当使用bfs:true时,这将返回到终止节点或结束节点过滤器中带有标签的最近n个节点的路径,其中n是给定的限制。

optional

BOOLEAN

false

路径扩展是否可选?如果设置为true,则当扩展通常会因没有结果而消除行时,将产生null值。

endNodes

LIST<NODE>

null

只有这些节点可以结束返回的路径,并且如果可能,扩展将继续通过这些节点。

terminatorNodes

LIST<NODE>

null

只有这些节点可以结束返回的路径,并且扩展不会继续通过这些节点。

allowlistNodes

LIST<NODE>

null

只有这些节点被允许在扩展中(如果存在,endNodes 和 terminatorNodes 也将被允许)。

denylistNodes

LIST<NODE>

null

返回的路径都不会包含这些节点。

whitelistNodes (已弃用)

LIST<NODE>

null

参见 allowlistNodes。

blacklistNodes (已弃用)

LIST<NODE>

null

参见 denylistNodes。

关系过滤器

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

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

输入 类型 方向

LIKES>

LIKES

出站

<FOLLOWS

FOLLOWS

入站

KNOWS

KNOWS

双向

>

任何类型

出站

<

任何类型

入站

标签过滤器

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

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

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

-

拒绝列表

-Foe

路径中的任何节点都不会具有拒绝列表中存在的标签。

+

允许列表

+Friend

路径中的所有节点都必须具有允许列表中的标签(如果使用终止节点和结束节点过滤器,则这些节点除外)。如果不存在允许列表操作符,则允许所有标签。

/

终止

/Friend

只返回到达具有给定标签的节点的路径,并停止超出该节点的进一步扩展。终止节点无需遵守允许列表。终止过滤优先于结束节点过滤。

>

结束节点

>Friend

只返回到达具有给定标签的节点的路径,但继续扩展以匹配超出其范围的结束节点。结束节点无需遵守允许列表即可返回,但只有当节点具有允许列表中的标签时,才允许超出其范围的扩展。

:

复合标签

Foe:Friend

这返回标签的合取,例如/Foo:Bar表示终止节点必须同时匹配FooBar。要在没有特殊含义的标签中包含:,请使用\进行转义,例如Foo\:Bar是标签Foo:Bar

标签过滤器操作符优先级和行为

允许多个标签过滤器操作符同时使用。以下面的示例为例

labelFilter:'+Person|Movie|-SciFi|>Western|/Romance'

如果我们分析这个标签过滤器,我们可以看到

  • :Person:Movie标签被允许

  • :SciFi被拒绝

  • :Western是一个结束节点标签

  • :Romance是一个终止标签。

操作符评估的优先级不取决于它们在labelFilter中的位置,而是固定的

拒绝列表过滤器-,终止过滤器/,结束节点过滤器>,允许列表过滤器+

这意味着

  • 返回路径的节点中永远不会出现被拒绝的标签-,即使相同的标签(或具有被拒绝标签的另一个节点的标签)包含在另一个过滤器列表中。

  • 如果使用终止过滤器/或结束节点过滤器>,则只返回到达具有这些标签的节点的路径作为结果。这些结束节点不受允许列表过滤器的限制。

  • 如果一个节点是终止节点/,则不会发生超出该节点的进一步扩展。

  • 允许列表仅适用于终止或结束节点过滤器中的节点(但不包括结束节点)。如果不存在结束节点或终止节点操作符,则允许列表适用于路径中的所有节点。

  • 如果labelFilter中不存在允许列表操作符,则视为所有标签都已允许。

唯一性

节点和关系的唯一性指导扩展和返回结果。下表描述了可用值

描述

RELATIONSHIP_PATH

对于每个返回的节点,都有一条从起始节点到该节点的(关系层面)唯一路径。这是 Cypher 的默认扩展模式。

NODE_GLOBAL

一个节点不能被遍历多次。这是旧版遍历框架的行为。

NODE_LEVEL

同一级别的实体保证是唯一的。

NODE_PATH

对于每个返回的节点,都有一条从起始节点到该节点的唯一路径。

NODE_RECENT

这类似于 NODE_GLOBAL,但只保证最近访问节点的唯一性,并可配置计数。遍历大型图非常占用内存,因为它会跟踪所有已访问的节点。对于大型图,遍历器可能会占用 JVM 中的所有内存,导致 OutOfMemoryError。与此唯一性一起,您可以提供一个计数,即最近访问节点的数量。这可能导致一个节点被多次访问,但可以无限扩展。

RELATIONSHIP_GLOBAL

一个关系不能被遍历多次,而节点可以。

RELATIONSHIP_LEVEL

同一级别的实体保证是唯一的。

RELATIONSHIP_RECENT

与 NODE_RECENT 相同,但适用于关系。

NONE

无限制(用户必须自行管理)

指定节点标签和关系类型的序列

路径扩展器过程可以基于标签、关系类型或两者的重复序列进行扩展。序列可以定义如下

  • 如果只使用标签序列,请使用labelFilter,但用逗号分隔重复序列中每个步骤的过滤条件。

  • 如果只使用关系序列,请使用relationshipFilter,但用逗号分隔重复序列中每个步骤的过滤条件。

  • 如果同时使用关系和标签序列,请使用sequence参数。

用法 配置参数 描述 语法 解释

仅标签序列

labelFilter

相同的语法和过滤器,但使用逗号 (,) 分隔序列中每个步骤的过滤器。

labelFilter:'Post|-Blocked,Reply,>Admin'

起始节点必须是未被阻塞的:Post节点,下一个节点必须是:Reply,再下一个必须是:Admin,然后如果可能则重复。只返回在序列该位置以:Admin节点结尾的路径。

仅关系序列

relationshipFilter

相同的语法,但使用逗号 (,) 分隔序列中每个关系遍历的过滤器。

relationshipFilter:'NEXT>,<FROM,POSTED>|REPLIED>'

扩展将首先从起始节点扩展NEXT>,然后是<FROM,接着是POSTED>REPLIED>,然后如果可能则重复。

关系和标签的序列

sequence

一个逗号分隔的交替标签和关系过滤器字符串,用于重复序列中的每个步骤。序列应以标签过滤器开头,并以关系过滤器结尾。如果存在,将忽略labelFilterrelationshipFilter,因为它具有更高的优先级。

sequence:'Post|-Blocked, NEXT>, Reply, <FROM, >Admin, POSTED>|REPLIED>'

结合了上述行为。

在某些用例中,序列不从起始节点开始,而是从距离起始节点一个节点的位置开始。

配置参数beginSequenceAtStart切换此行为。其默认值为true。如果设置为false,则会更改labelFilterrelationshipFiltersequence的预期值,如下所述

sequence 更改后的行为 示例 解释

labelFilter

起始节点不被视为序列的一部分。序列从距离起始节点一个节点的位置开始。

beginSequenceAtStart:false, labelFilter:'Post|-Blocked,Reply,>Admin'

从起始节点出来的下一个节点值开始序列(并且必须是未被阻塞的:Post节点),并且只返回以Admin节点结尾的路径。

relationshipFilter

序列字符串中的第一个关系过滤器将不被视为重复序列的一部分,而只用于从起始节点到将成为序列实际起始的节点的第一个关系。

beginSequenceAtStart:false, relationshipFilter:'FIRST>,NEXT>,<FROM,POSTED>|REPLIED>'

FIRST>将仅从起始节点遍历到将成为重复NEXT>,<FROM,POSTED>|REPLIED>序列起始的节点。

sequence

结合了上述两种行为。

beginSequenceAtStart:false, sequence:'FIRST>, Post|-Blocked, NEXT>, Reply, <FROM, >Admin, POSTED>|REPLIED>'

结合了上述行为。

序列提示

序列中的标签过滤与endNodes+terminatorNodes协同工作,但节点的包含必须是一致的。

如果需要限制序列重复的次数,可以使用maxLevel配置参数来完成(将迭代次数乘以序列中节点的大小)。

示例

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

MERGE (mark:Person:DevRel {name: "Mark"})
MERGE (lju:Person:DevRel {name: "Lju"})
MERGE (praveena:Person:Engineering {name: "Praveena"})
MERGE (zhen:Person:Engineering {name: "Zhen"})
MERGE (martin:Person:Engineering {name: "Martin"})
MERGE (joe:Person:Field {name: "Joe"})
MERGE (stefan:Person:Field {name: "Stefan"})
MERGE (alicia:Person:Product {name: "Alicia"})
MERGE (jake:Person:Product {name: "Jake"})
MERGE (john:Person:Product {name: "John"})
MERGE (jonny:Person:Sales {name: "Jonny"})
MERGE (anthony:Person:Sales {name: "Anthony"})
MERGE (rik:Person:Sales {name: "Rik"})

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 (jonny)-[:KNOWS]-(anthony)
MERGE (john)-[:KNOWS]-(rik)

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)
MERGE (lju)-[:FOLLOWS]->(jake)
MERGE (alicia)-[:FOLLOWS]->(jonny)
MERGE (zhen)-[:FOLLOWS]->(john)
MERGE (anthony)-[:FOLLOWS]->(joe)

下面的 Neo4j Browser 可视化显示了示例图

apoc.path.expandConfig
图 1. 示例图

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

FOLLOWS关系具有方向,因此在使用时我们将指定方向。

关系类型和节点标签过滤器

让我们从 Praveena 节点开始扩展路径。我们只想考虑KNOWS关系类型,因此我们将其指定为relationshipFilter参数。

以下返回 Praveena KNOWS 的人,从 1 到 2 跳的路径
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	relationshipFilter: "KNOWS",
    minLevel: 1,
    maxLevel: 2
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 2. 结果
路径 跳数

(: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标签的路径,我们将向labelFilter参数提供值+Engineering

以下返回仅包含 Praveena KNOWSEngineering人员,从 1 到 2 跳的路径
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	relationshipFilter: "KNOWS",
	labelFilter: "+Engineering",
    minLevel: 1,
    maxLevel: 2
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 3. 结果
路径 跳数

(: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.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 4. 结果
路径 跳数

(: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 的连接非常广!

我们可以在从 Alicia 出发的路径中看到返回路径的 Neo4j Browser 可视化。

apoc.path.expandConfig.alicia
图 2. 从 Alicia 出发的路径

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

以下返回包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳的路径,一旦到达带有Engineering标签的节点即终止
MATCH (p:Person {name: "Alicia"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    labelFilter: "/Engineering",
    minLevel: 1,
    maxLevel: 3
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 5. 结果
路径 跳数

(: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.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    labelFilter: ">Engineering",
    minLevel: 1,
    maxLevel: 3
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 6. 结果
路径 跳数

(: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!

终止节点和结束节点

除了为遍历指定终止标签和结束标签外,我们还可以指定终止节点和结束节点。

让我们基于之前找到 Alicia KNOWSFOLLOWS 的人的查询。我们希望任何返回的路径在遇到 Joe 节点时立即停止,这可以通过将 Joe 节点传递给terminatorNodes参数来实现。

以下返回包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳的路径,一旦到达 Joe 即终止
MATCH (p:Person {name: "Alicia"})
MATCH (joe:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3,
    terminatorNodes: [joe]
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 7. 结果
路径 跳数

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

1

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

3

Alicia FOLLOWS Joe,但还有一条路径通过 Jonny 和 Anthony。

终止节点方法不一定能找到 Alicia 和 Joe 之间存在的所有路径。可能存在其他路径两次经过 Joe 节点。我们可以通过将 Joe 节点传递给endNodes参数来找到这些路径。如果使用此参数,所有返回的路径都将以 Joe 节点结束,但扩展将继续通过此节点,尝试找到其他以 Joe 结束的路径。

以下返回包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳的路径,其中路径在到达 Joe 时结束
MATCH (p:Person {name: "Alicia"})
MATCH (joe:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3,
    endNodes: [joe]
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 8. 结果
路径 跳数

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

1

(: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:Sales {name: "Jonny"})-[:KNOWS]→(:Person:Sales {name: "Anthony"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})

3

我们得到了使用终止节点方法得到的两条路径,从 Alicia 到 Joe,以及从 Alicia 到 Jonny 再到 Jonny 再到 Joe。但我们还有一条额外路径,从 Alicia 到 Joe 再到 Praveena 再到 Joe。

允许列表节点和拒绝列表节点

也可以指定允许列表和拒绝列表节点。

让我们基于之前找到 Alicia KNOWSFOLLOWS 的人的查询。我们希望任何返回的路径只包含节点 Mark、Joe、Zhen 和 Praveena,这可以通过将这些节点传递给allowlistNodes参数来实现。

以下返回从 Alicia 开始,遵循FOLLOWSKNOWS关系类型,从 1 到 3 跳的路径,只包含 Mark、Joe、Zhen 和 Praveena 的路径
MATCH (p:Person {name: "Alicia"})
MATCH (allowlist:Person)
WHERE allowlist.name IN ["Mark", "Joe", "Zhen", "Praveena"]
WITH p, collect(allowlist) AS allowlistNodes
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3,
    allowlistNodes: allowlistNodes
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 9. 结果
路径 跳数

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

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: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"})-[: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

在允许列表中,唯一与 Alicia 有直接连接的人是 Joe,所以所有路径都经过他。然后我们从 Joe 到其他人,再在他们之间进行 3 跳的路径。

我们可以在从 Alicia 到 Mark、Joe、Zhen 和 Praveena 的路径中看到返回路径的 Neo4j Browser 可视化。

apoc.path.expandConfig.allowlist
图 3. 从 Alicia 到 Mark、Joe、Zhen 和 Praveena 的路径

拒绝列表用于从返回的路径中排除节点。如果想排除包含 Joe 的路径,可以通过将 Joe 节点传递给denylistNodes参数来实现。

以下返回包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳的路径,排除包含 Joe 的路径
MATCH (p:Person {name: "Alicia"})
MATCH (joe:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3,
    denylistNodes: [joe]
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 10. 结果
路径 跳数

(: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: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"})-[:KNOWS]→(:Person:Product {name: "Jake"})←[:KNOWS]-(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

这返回的路径集非常小,因为 Joe 是连接 Alicia 到图其余部分的一个非常关键的节点。

我们可以在从 Alicia 出发但不包含 Joe 的路径中看到返回路径的 Neo4j Browser 可视化。

apoc.path.expandConfig.denylist
图 4. 从 Alicia 出发但不包含 Joe 的路径

广度优先搜索和深度优先搜索

我们可以通过指定bfs: true来控制遍历是否使用广度优先搜索 (BFS),或者通过指定bfs: false来控制是否使用深度优先搜索 (DFS) 算法。这通常与limit参数结合使用,以根据所选算法查找最近的节点。

以下使用 BFS 返回 10 条路径,包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳
MATCH (p:Person {name: "Alicia"})
MATCH (joe:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 5,
    bfs: true,
    limit: 10
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 11. 结果
路径 跳数

(: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

从这些结果中我们可以看到,路径在进入下一个级别之前,在每个级别上都完全扩展。例如,我们首先从

  • Alicia Joe

  • Alicia Jonny

  • Alicia Jake

然后从这些节点开始跟随关系。一旦它扩展了级别 2 的所有内容,它将接着探索级别 3。

apoc.path.expandConfig.alicia.bfs
图 5. 使用广度优先搜索从 Alicia 出发的路径

如果我们使用深度优先搜索算法,遍历将尽可能远地(直到maxLevel跳数)沿着特定路径向下,然后返回并探索其他路径。

以下使用 DFS 返回 10 条路径,包含 Alicia FOLLOWSKNOWS 的人员,从 1 到 3 跳
MATCH (p:Person {name: "Alicia"})
MATCH (joe:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3,
    bfs: false,
    limit: 10
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 12. 结果
路径 跳数

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

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: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

现在我们得到了不同的返回路径集。我们甚至看不到从 Alicia 到 Jonny 或从 Alicia 到 Jake 的路径,因为我们 10 条路径的限制完全被通过 Joe 的路径占据了。

我们可以在使用深度优先搜索从 Alicia 出发的路径中看到返回路径的 Neo4j Browser 可视化。

apoc.path.expandConfig.alicia.dfs
图 6. 使用深度优先搜索从 Alicia 出发的路径

唯一性

我们可以通过uniqueness参数指定遍历使用的唯一性策略。有关有效策略列表,请参阅唯一性。默认值为RELATIONSHIP_PATH

在本节中,我们将编写从 Joe 开始并遍历FOLLOWS关系的查询。

以下返回从 Joe 开始并遍历FOLLOWS关系类型,从 1 到 3 跳的路径中的节点
MATCH (p:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>",
    minLevel: 1,
    maxLevel: 3,
    uniqueness: "RELATIONSHIP_PATH" // default
})
YIELD path
RETURN [node in nodes(path) | node.name] AS nodes, length(path) AS hops
ORDER BY hops;
表 13. 结果
节点 跳数

["Joe", "Zhen"]

1

["Joe", "Praveena"]

1

["Joe", "Mark"]

1

["Joe", "Zhen", "John"]

2

["Joe", "Praveena", "Joe"]

2

["Joe", "Mark", "Stefan"]

2

["Joe", "Praveena", "Joe", "Zhen"]

3

["Joe", "Praveena", "Joe", "Mark"]

3

["Joe", "Mark", "Stefan", "Joe"]

3

返回的几条路径包含 Joe 节点两次。如果我们要确保路径中的节点是唯一的,可以使用NODE_PATH策略。

以下返回从 Joe 开始并遍历FOLLOWS关系类型,从 1 到 3 跳的路径中的节点,使用NODE_PATH策略
MATCH (p:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
    relationshipFilter: "FOLLOWS>",
    minLevel: 1,
    maxLevel: 3,
    uniqueness: "NODE_PATH"
})
YIELD path
RETURN [node in nodes(path) | node.name] AS nodes, length(path) AS hops
ORDER BY hops;
表 14. 结果
节点 跳数

["Joe", "Zhen"]

1

["Joe", "Praveena"]

1

["Joe", "Mark"]

1

["Joe", "Zhen", "John"]

2

["Joe", "Mark", "Stefan"]

2

现在返回的路径具有唯一的节点列表。

关系类型序列

关系类型序列可以通过逗号分隔传递给relationshipFilter的值来指定。

例如,如果我们要从 Joe 节点开始,并遍历出站方向的FOLLOWS关系和任一方向的KNOWS关系的序列,我们可以指定关系过滤器FOLLOWS>,KNOWS

以下返回从 Joe 开始,关系类型在FOLLOWSKNOWS之间交替,从 1 到 4 跳的路径
MATCH (p:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
	relationshipFilter: "FOLLOWS>,KNOWS",
	beginSequenceAtStart: true,
	minLevel: 1,
	maxLevel: 4
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 15. 结果
路径 跳数

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

1

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

1

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

1

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

2

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

2

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

2

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

2

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

2

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

2

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

3

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

3

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

3

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

3

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

4

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

4

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

4

minLevelmaxLevel值指的是路径中关系的跳数。使用minLevel为 1 意味着将返回从 Joe 出发,带有FOLLOWS关系类型,且跳数为 1 的路径。如果我们要确保此relationshipFilter中定义的关系类型序列至少匹配一次,我们需要使用minLevel2,因为过滤器中有两种关系类型。

以下返回从 Joe 开始,关系类型在FOLLOWSKNOWS之间交替,从 2 到 4 跳的路径
MATCH (p:Person {name: "Joe"})
CALL apoc.path.expandConfig(p, {
	relationshipFilter: "FOLLOWS>,KNOWS",
	beginSequenceAtStart: true,
	minLevel: 2,
	maxLevel: 4
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 16. 结果
路径 跳数

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

2

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

2

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

2

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

2

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

2

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

2

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

3

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

3

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

3

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

3

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

4

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

4

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

4

此配置还可以与beginSequenceAtStart: false结合使用,这意味着序列将从距离起始节点一跳的位置开始。如果使用此配置,则relationshipFilter中定义的第一个关系类型将仅适用于起始节点。

以下返回从 Jake 开始,在首先跟随 Jake 的KNOWS关系后,关系类型在FOLLOWSKNOWS之间交替,从 3 到 5 跳的路径
MATCH (p:Person {name: "Jake"})
CALL apoc.path.expandConfig(p, {
	relationshipFilter: "KNOWS,FOLLOWS>,KNOWS",
	beginSequenceAtStart: false,
	minLevel: 3,
	maxLevel: 7
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 17. 结果
路径 跳数

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

3

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

3

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

4

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

4

(:Person:Product {name: "Jake"})←[:KNOWS]-(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})←[:KNOWS]-(:Person:Engineering {name: "Zhen"})-[:FOLLOWS]→(:Person:Product {name: "John"})-[:KNOWS]→(:Person:Sales {name: "Rik"})

5

节点标签序列

节点标签序列可以通过逗号分隔传递给labelFilter的值来指定。这通常与beginSequenceAtStart: false结合使用,这意味着序列将从距离起始节点一跳的位置开始。

例如,如果从 Praveena 节点开始,并希望返回包含交替FieldDevRel节点的路径,我们可以指定标签过滤器为"+Field,+DevRel"

以下返回从 Praveena 开始,节点在具有FieldDevRel标签之间交替,从 1 到 4 跳的路径。
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	labelFilter: "+Field,+DevRel",
	beginSequenceAtStart: false,
	minLevel: 1,
	maxLevel: 4
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 18. 结果
路径 跳数

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

1

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

1

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})

2

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

2

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

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

3

minLevelmaxLevel值指的是路径中关系的跳数。使用minLevel为 1 意味着将返回距离 Praveena 一跳的节点具有Field标签的路径。如果我们要确保此labelFilter中定义的标签序列至少匹配一次,我们需要使用minLevel2

以下返回从 Praveena 开始,节点在具有FieldDevRel标签之间交替,从 2 到 4 跳的路径。
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	labelFilter: "+Field,+DevRel",
	beginSequenceAtStart: false,
	minLevel: 2,
	maxLevel: 4
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 19. 结果
路径 跳数

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})

2

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

2

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

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

3

现在,仅包含从 Praveena 到 Joe 的关系的路径已被过滤掉。

但是,如果我们不想指定多个标签的存在,而是想找到节点不包含某个标签的路径呢?要查找包含交替的Field节点和非Field节点的路径,我们可以指定标签过滤器为"+Field,-Field"

以下返回从 Praveena 开始,节点在具有Field标签和不具有Field标签之间交替,从 1 到 4 跳的路径中的节点
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	labelFilter: "+Field,-Field",
	beginSequenceAtStart: false,
	minLevel: 2,
	maxLevel: 4
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
表 20. 结果
路径 跳数

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})←[:FOLLOWS]-(:Person:Sales {name: "Anthony"})

2

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

2

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

2

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})

2

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

2

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})←[:关注]-(:人物:销售部 {姓名: "Anthony"})

2

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Zhen"})

2

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})←[:关注]-(:人物:产品部 {姓名: "Alicia"})

2

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Praveena"})

2

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

2

(:人物:工程部 {姓名: "Praveena"})←[:关注]-(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Zhen"})-[:认识]→(:人物:现场部 {姓名: "Stefan"})

3

(:Person:Engineering {name: "Praveena"})←[:FOLLOWS]-(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})

3

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Zhen"})-[:认识]→(:人物:现场部 {姓名: "Stefan"})

3

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

3

(:人物:工程部 {姓名: "Praveena"})←[:关注]-(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Zhen"})-[:认识]→(:人物:现场部 {姓名: "Stefan"})←[:关注]-(:人物:开发者关系部 {姓名: "Mark"})

4

(:人物:工程部 {姓名: "Praveena"})←[:关注]-(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:开发者关系部 {姓名: "Mark"})-[:关注]→(:人物:现场部 {姓名: "Stefan"})←[:认识]-(:人物:工程部 {姓名: "Zhen"})

4

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:工程部 {姓名: "Zhen"})-[:认识]→(:人物:现场部 {姓名: "Stefan"})←[:关注]-(:人物:开发者关系部 {姓名: "Mark"})

4

(:人物:工程部 {姓名: "Praveena"})-[:关注]→(:人物:现场部 {姓名: "Joe"})-[:关注]→(:人物:开发者关系部 {姓名: "Mark"})-[:关注]→(:人物:现场部 {姓名: "Stefan"})←[:认识]-(:人物:工程部 {姓名: "Zhen"})

4

我们还有更多路径,路径长度在2到4跳之间。这些路径具有以下标签:

  • 2跳 - Field → 非Field

  • 3跳 - Field → 非FieldField

  • 4跳 - Field → 非FieldField → 非Field

这些路径有点难以阅读,因此我们可以使用 nodes 函数只返回节点来简化输出。我们还会过滤结果,以便只返回与完整 +Field,-Field 标签过滤器匹配的路径。这可以通过只返回偶数长度的路径来实现。

以下返回从 Praveena 开始的1到4跳路径的节点,其中节点在具有 Field 标签和不具有 Field 标签之间交替:
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	labelFilter: "+Field,-Field",
	beginSequenceAtStart: false,
	minLevel: 2,
	maxLevel: 4
})
YIELD path
WHERE length(path) % 2 = 0

// Remove the Praveena node from the returned path
RETURN nodes(path)[1..] AS nodes, length(path) AS hops

ORDER BY hops;
表 21. 结果
节点 跳数

[(:人物:现场部 {姓名: "Joe"}), (:人物:销售部 {姓名: "Anthony"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Zhen"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:产品部 {姓名: "Alicia"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Praveena"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:销售部 {姓名: "Anthony"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Zhen"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:产品部 {姓名: "Alicia"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Praveena"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Zhen"}), (:人物:现场部 {姓名: "Stefan"}), (:人物:开发者关系部 {姓名: "Mark"})]

4

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"}), (:人物:现场部 {姓名: "Stefan"}), (:人物:工程部 {姓名: "Zhen"})]

4

[(:人物:现场部 {姓名: "Joe"}), (:人物:工程部 {姓名: "Zhen"}), (:人物:现场部 {姓名: "Stefan"}), (:人物:开发者关系部 {姓名: "Mark"})]

4

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"}), (:人物:现场部 {姓名: "Stefan"}), (:人物:工程部 {姓名: "Zhen"})]

4

在节点序列中,* 字符可用作通配符,表示该位置可以出现任何标签。如果我们要匹配任意标签后跟 DevRel 标签的节点序列,可以指定标签过滤器 *,+DevRel

以下返回从 Praveena 开始的2到4跳路径的节点,其中节点在具有任意标签和 DevRel 标签之间交替:
MATCH (p:Person {name: "Praveena"})
CALL apoc.path.expandConfig(p, {
	labelFilter: "*,+DevRel",
	beginSequenceAtStart: false,
	minLevel: 2,
	maxLevel: 4
})
YIELD path
WHERE length(path) % 2 = 0

// Remove the Praveena node from the returned path
RETURN nodes(path)[1..] AS nodes, length(path) AS hops

ORDER BY hops;
表 22. 结果
节点 跳数

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"})]

2

[(:人物:工程部 {姓名: "Zhen"}), (:人物:开发者关系部 {姓名: "Lju"})]

2

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"}), (:人物:产品部 {姓名: "Jake"}), (:人物:开发者关系部 {姓名: "Lju"})]

4

[(:人物:现场部 {姓名: "Joe"}), (:人物:开发者关系部 {姓名: "Mark"}), (:人物:产品部 {姓名: "Jake"}), (:人物:开发者关系部 {姓名: "Lju"})]

4

[(:人物:工程部 {姓名: "Zhen"}), (:人物:开发者关系部 {姓名: "Lju"}), (:人物:产品部 {姓名: "Jake"}), (:人物:开发者关系部 {姓名: "Mark"})]

4