apoc.path.expandConfig

此过程不建议在多线程环境中运行。因此,并行运行时不支持此过程。有关详细信息,请参阅 Cypher 手册 → 并行运行时

详细信息

语法

apoc.path.expandConfig(startNode, config) :: (path)

描述

返回从起始 NODE 开始,根据给定的 RELATIONSHIP 类型进行扩展(从最小深度到最大深度)的 PATH 值。

输入参数

名称

类型

描述

startNode

ANY

算法开始的起始节点。startNode 可以是 STRING (elementId())、INTEGER (id())、NODE 或 `LIST<STRING | INTEGER | NODE> 类型。

config

MAP

{ minLevel = -1 :: INTEGER, maxLevel = -1 :: INTEGER, relationshipFilter :: STRING, labelFilter :: STRING, beginSequenceAtStart = true :: BOOLEAN, uniqueness = ""RELATIONSHIP_PATH"" :: STRING, bfs = true :: BOOLEAN, filterStartNode = false :: BOOLEAN, limit = -1 :: INTEGER, optional = false :: BOOLEAN, endNodes :: LIST<NODES>, terminatorNodes:: LIST<NODES>, allowlistNodes:: LIST<NODES>, denylistNodes:: LIST<NODES> }

返回参数

名称

类型

描述

path

PATH

扩展后的路径。

配置参数

此过程支持以下配置参数

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

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[>]|…​

input type 方向

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 中没有出现白名单运算符,则视为所有标签都在白名单中。

唯一性

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

description(描述)

RELATIONSHIP_PATH

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

NODE_GLOBAL

一个节点不能被遍历超过一次。这与传统遍历框架的行为一致。

NODE_LEVEL

同一层级上的实体保证是唯一的。

NODE_PATH

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

NODE_RECENT

这类似于 NODE_GLOBAL,但仅保证最近访问的节点之间的唯一性,且计数可配置。遍历巨大的图是非常占用内存的,因为它需要跟踪所有已访问的节点。对于巨大的图,遍历器可能会占用 JVM 中的所有内存,从而导致 OutOfMemoryError。结合此唯一性,您可以提供一个计数,即最近访问的节点数量。这可能会导致节点被访问多次,但可以实现无限扩展。

RELATIONSHIP_GLOBAL

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

RELATIONSHIP_LEVEL

同一层级上的实体保证是唯一的。

RELATIONSHIP_RECENT

与 NODE_RECENT 相同,但针对关系。

NONE

无限制(用户需要自行管理)

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

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

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

  • 如果仅使用关系序列,请使用 relationshipFilter,但使用逗号分隔重复序列中每一次关系遍历的过滤条件。

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

用法 配置参数 description(描述) 语法 说明

仅标签序列

labelFilter

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

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

起始节点必须是 :Post 节点且不能是 :Blocked,下一个节点必须是 :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'

从起始 NODE 出发的下一个 NODE 值开始序列(必须是 :Post 节点且不能是 :Blocked),并且仅返回以 Admin 节点结束的路径。

relationshipFilter

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

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

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

sequence

结合上述两种行为。

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

结合上述行为。

序列提示

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

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

输出参数

名称 类型

path

PATH

使用示例

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

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

以下查询返回仅包含 Engineering 人员的路径,这些人是 Praveena KNOWS 的,跳数为 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;
结果
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.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    minLevel: 1,
    maxLevel: 3
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
结果
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 的人脉非常广!

我们可以在 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;
结果
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.expandConfig(p, {
    relationshipFilter: "FOLLOWS>|KNOWS",
    labelFilter: ">Engineering",
    minLevel: 1,
    maxLevel: 3
})
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
结果
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!

终止节点和结束节点

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

让我们基于之前找到 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;
结果
path 跳数

(: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;
结果
path 跳数

(: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 再到 Joe。但我们也得到了一条额外的路径,从 Alicia 到 Joe 再到 Praveena 再回到 Joe。

白名单节点和黑名单节点

也可以指定白名单和黑名单节点。

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

以下查询返回遵循 FOLLOWSKNOWS 关系类型且跳数为 1 到 3 跳的 Alicia 路径,仅包含包含 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;
结果
path 跳数

(: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;
结果
path 跳数

(: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 参数结合使用,以基于所选算法找到最近的节点。

以下查询返回 10 条包含 Alicia FOLLOWSKNOWS 的人员路径,跳数为 1 到 3 跳,使用 BFS
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;
结果
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

从这些结果中我们可以看到,路径在进入下一层之前已在每一层完全扩展。例如,我们首先从以下位置扩展:

  • Alicia Joe

  • Alicia Jonny

  • Alicia Jake

然后跟随来自这些节点的关系。一旦扩展了第 2 层的所有内容,它将探索第 3 层。

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

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

以下查询返回 10 条包含 Alicia FOLLOWSKNOWS 的人员路径,跳数为 1 到 3 跳,使用 DFS
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;
结果
path 跳数

(: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;
结果
节点 跳数

["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;
结果
节点 跳数

["Joe", "Zhen"]

1

["Joe", "Praveena"]

1

["Joe", "Mark"]

1

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

2

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

2

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

关系类型的序列

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

例如,如果我们想从 Joe 节点开始,并在传出方向上遍历 FOLLOWS 关系,并在任一方向上遍历 KNOWS 关系,我们可以指定关系过滤器 FOLLOWS>,KNOWS

以下查询返回从 Joe 开始的 1 到 4 跳路径,其中关系类型在 FOLLOWSKNOWS 之间交替
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;
结果
path 跳数

(: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 关系类型的路径。如果我们想确保此 relationshipFilter 中定义的关系类型序列至少匹配一次,我们需要使用 minLevel 为 2,因为过滤器中有两种关系类型。

以下查询返回从 Joe 开始的 2 到 4 跳路径,其中关系类型在 FOLLOWSKNOWS 之间交替
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;
结果
path 跳数

(: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 开始的 3 到 5 跳路径,其中关系类型在 FOLLOWSKNOWS 之间交替,在首先从 Jake 跟随 KNOWS 关系之后
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;
结果
path 跳数

(: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 开始的 1 到 4 跳路径,其中节点在具有 FieldDevRel 标签之间交替。
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;
结果
path 跳数

(: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 中定义的标签序列至少匹配一次,我们需要使用 minLevel 为 2。

以下查询返回从 Praveena 开始的 2 到 4 跳路径,其中节点在具有 FieldDevRel 标签之间交替。
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;
结果
path 跳数

(: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 开始的 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
RETURN path, length(path) AS hops
ORDER BY hops;
结果
path 跳数

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

(: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:Engineering {name: "Praveena"})

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: "Zhen"})-[:KNOWS]→(: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

(:Person:Engineering {name: "Praveena"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:Engineering {name: "Zhen"})-[:KNOWS]→(: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

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

4

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

4

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

4

(:Person:Engineering {name: "Praveena"})-[:FOLLOWS]→(:Person:Field {name: "Joe"})-[:FOLLOWS]→(:Person:DevRel {name: "Mark"})-[:FOLLOWS]→(:Person:Field {name: "Stefan"})←[:KNOWS]-(:Person:Engineering {name: "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;
结果
节点 跳数

[(:Person:Field {name: "Joe"}), (:Person:Sales {name: "Anthony"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Zhen"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Product {name: "Alicia"})]

2

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Praveena"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Sales {name: "Anthony"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Zhen"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Product {name: "Alicia"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Praveena"})]

2

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"})]

2

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Zhen"}), (:Person:Field {name: "Stefan"}), (:Person:DevRel {name: "Mark"})]

4

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"}), (:Person:Field {name: "Stefan"}), (:Person:Engineering {name: "Zhen"})]

4

[(:Person:Field {name: "Joe"}), (:Person:Engineering {name: "Zhen"}), (:Person:Field {name: "Stefan"}), (:Person:DevRel {name: "Mark"})]

4

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"}), (:Person:Field {name: "Stefan"}), (:Person:Engineering {name: "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;
结果
节点 跳数

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"})]

2

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"})]

2

[(:Person:Engineering {name: "Zhen"}), (:Person:DevRel {name: "Lju"})]

2

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"}), (:Person:Product {name: "Jake"}), (:Person:DevRel {name: "Lju"})]

4

[(:Person:Field {name: "Joe"}), (:Person:DevRel {name: "Mark"}), (:Person:Product {name: "Jake"}), (:Person:DevRel {name: "Lju"})]

4

[(:Person:Engineering {name: "Zhen"}), (:Person:DevRel {name: "Lju"}), (:Person:Product {name: "Jake"}), (:Person:DevRel {name: "Mark"})]

4