Cypher 投影

Cypher 投影通过 Cypher 查询上下文创建内存中图。使用 Cypher 投影,您可以从一个或多个 Neo4j 数据库读取数据、加载本地或远程文件,或者即时创建数据。

Cypher 投影包含两个主要部分

  1. 一个或多个用于构造一组节点或源-目标节点对的子句。

  2. gds.graph.project 函数的调用。

有关常见的投影模式,请参阅示例部分。

相比原生投影,Cypher 投影更灵活且表达能力更强。

注意事项

生命周期

投影后的图驻留在内存中(在图目录中),直到发生以下任一情况:

  • 使用 gds.graph.drop 过程删除了该图。

  • 投影该图的 Neo4j 数据库被停止或删除。

  • Neo4j DBMS 被停止。

节点属性支持

Cypher 投影只能从 Cypher 查询中投影一组有限的节点属性类型。节点属性页面详细说明了支持哪些节点属性类型。其他类型的节点属性必须转换为受支持的类型或编码为受支持的类型,才能使用 Cypher 投影进行投影。

节点属性和标签的选择

如果一个节点出现多次,则投影将使用第一次出现时的节点属性和标签。当一个节点既可以是源节点也可以是目标节点,且它们的配置不同时,这一点非常重要。相关的配置选项有 sourceNodePropertiestargetNodePropertiessourceNodeLabelstargetNodeLabels

并行 Cypher 运行时

Cypher 投影与并行运行时兼容,可用于加快投影执行速度。所实现的加速取决于查询的可并行化程度。请注意,并行运行时仅在 Neo4j 企业版中提供,且从 5.13 版本开始提供。

语法

Cypher 投影是对正在投影的关系的聚合函数;因此,它返回一个包含投影图相关信息的对象。

投影函数接受两个强制参数:graphNamesourceNode。第三个参数是 targetNode,通常会提供。该参数是可选的,可以为 null,用于投影孤立节点。接下来的第四个可选参数 dataConfig 可用于投影节点属性和标签,以及关系属性和类型。最后一个(第五个)可选参数 configuration 可用于投影的通用配置,例如 readConcurrency

RETURN gds.graph.project(
    graphName: String,
    sourceNode: Node or Integer,
    targetNode: Node or Integer,
    dataConfig: Map,
    configuration: Map
) YIELD
    graphName: String,
    nodeCount: Integer,
    relationshipCount: Integer,
    projectMillis: Integer,
    query: String,
    configuration: Map
表 1. 参数
名称 可选 描述

graphName

图在目录中存储时所使用的名称。

sourceNode

关系的源节点。不能为空。

targetNode

关系的目标节点。targetNode 可以为 null(例如由于 OPTIONAL MATCH),在这种情况下,源节点作为孤立节点被投影。

dataConfig

源节点和目标节点的属性与标签配置,以及关系的属性和类型配置。

配置

用于配置投影的附加参数。

表 2. 数据配置
名称 类型 默认 描述

sourceNodeProperties

Map

{}

源节点的属性。

targetNodeProperties

Map

{}

目标节点的属性。

sourceNodeLabels(源节点标签)

字符串列表或字符串

[]

源节点的标签。

targetNodeLabels(目标节点标签)

字符串列表或字符串

[]

目标节点的标签。

relationshipProperties

Map

{}

关系的属性。

关系类型 (relationshipType)

字符串

'*'

关系的类型。

表 3. 配置
名称 类型 默认 可选 描述

readConcurrency

整数

4 [1]

创建图时使用的并发线程数。

undirectedRelationshipTypes

字符串列表

[]

将多个关系类型声明为无向。具有指定类型的关系将作为无向关系导入。可以使用 * 将所有关系类型声明为无向。

inverseIndexedRelationshipTypes

字符串列表

[]

声明多个也将进行反向索引的关系类型。可以使用 * 将所有关系类型声明为反向索引。

内存 Aura 图分析

字符串

-

[2]

声明为投影图创建的 GDS 会话所使用的内存。

TTL Aura 图分析

Duration

PT1H

声明为投影图创建的 GDS 会话因不活动而过期之前的生存时间。

sessionId Aura 图分析

字符串

-

[2]

应该在其上投影图形的 GDS 会话的 ID。

batchSize Aura 图分析

整数

10000

从 DBMS 传输到会话的批处理大小。降低该值可减少 DBMS 端的内存使用。增加该值可减少发送到 GDS 会话的批次数。

1. 在 GDS 会话 中,默认值为可用处理器数量。

2. 仅对 Aura 图分析必需

表 4. 结果
名称 类型 描述

graphName

字符串

图在目录中存储时所使用的名称。

nodeCount

整数

投影图中存储的节点数。

relationshipCount

整数

投影图中存储的关系数。

projectMillis

整数

投影图所耗费的毫秒数。

query

字符串

此投影使用的查询。

配置

整数

此投影使用的配置。

要获取已存储图的信息(例如其模式),可以使用 gds.graph.list

示例

以下所有示例应在空数据库中运行。

为了演示 GDS Cypher 聚合,我们将创建一个小型社交网络图。示例图如下所示

Visualization of the example graph
以下 Cypher 语句将在 Neo4j 数据库中创建示例图:
CREATE
  (florentin:Person { name: 'Florentin', age: 16 }),
  (adam:Person { name: 'Adam', age: 18 }),
  (veselin:Person { name: 'Veselin', age: 20, ratings: [5.0] }),
  (hobbit:Book { name: 'The Hobbit', isbn: 1234, numberOfPages: 310, ratings: [1.0, 2.0, 3.0, 4.5] }),
  (frankenstein:Book { name: 'Frankenstein', isbn: 4242, price: 19.99 }),

  (florentin)-[:KNOWS { since: 2010 }]->(adam),
  (florentin)-[:KNOWS { since: 2018 }]->(veselin),
  (florentin)-[:READ { numberOfPages: 4 }]->(hobbit),
  (florentin)-[:READ { numberOfPages: 42 }]->(hobbit),
  (adam)-[:READ { numberOfPages: 30 }]->(hobbit),
  (veselin)-[:READ]->(frankenstein)

简单图

简单图是仅包含一个节点标签和一个关系类型的图,即单部图。我们将首先演示如何通过仅投影 Person 节点标签和 KNOWS 关系类型来加载简单图。

投影 Person 节点和 KNOWS 关系
MATCH (source:Person)-[r:KNOWS]->(target:Person)
WITH gds.graph.project('persons', source, target) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 5. 结果
graph(图) 节点 rels

"persons"

3

2

带有孤立节点的图

为了投影未连接的节点,我们可以使用 OPTIONAL MATCH。为了演示,我们投影所有节点,其中一些节点可能通过 KNOWS 关系类型连接。

投影所有节点和 KNOWS 关系
MATCH (source) OPTIONAL MATCH (source)-[r:KNOWS]->(target)
WITH gds.graph.project('persons', source, target) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 6. 结果
graph(图) 节点 rels

"persons"

5

2

使用并行运行时

Cypher 投影与并行运行时兼容。

使用并行运行时进行投影
CYPHER runtime=parallel
MATCH (source:Person)-[r:KNOWS]->(target:Person)
WITH gds.graph.project('persons', source, target) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 7. 结果
graph(图) 节点 rels

"persons"

3

2

任意源和目标 ID 值

到目前为止,这些示例展示了如何基于现有节点投影图。也可以直接传递整数值。

投影任意 ID 值
UNWIND [ [42, 84], [13, 37], [19, 84] ] AS sourceAndTarget
WITH sourceAndTarget[0] AS source, sourceAndTarget[1] AS target
WITH gds.graph.project('arbitrary', source, target) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 8. 结果
graph(图) 节点 rels

"arbitrary"

5

3

投影图无法再将投影节点连接到基础数据库中的现有节点。因此,无法在此图上执行 .write 过程。

多图

多重图是具有多个节点标签和关系类型的图。

为了在加载多个节点标签时保留标签,我们可以将 sourceNodeLabels 键和 targetNodeLabels 键添加到第四个 dataConfig 参数中。— 为了在加载多个关系类型时保留类型信息,我们可以将 relationshipType 键添加到第四个 dataConfig 参数中。

投影 PersonBook 节点以及 KNOWSREAD 关系
MATCH (source)
WHERE source:Person OR source:Book
OPTIONAL MATCH (source)-[r:KNOWS|READ]->(target)
WHERE target:Person OR target:Book
WITH gds.graph.project(
  'personsAndBooks',
  source,
  target,
  {
    sourceNodeLabels: labels(source),
    targetNodeLabels: labels(target),
    relationshipType: type(r)
  }
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 9. 结果
graph(图) 节点 rels

"personsAndBooks"

5

6

sourceNodeLabelstargetNodeLabels 的值可以是以下之一

表 10. sourceNodeLabelstargetNodeLabels 的值
type 示例 description(描述)

字符串列表

labels(s)['A', 'B']

将该列表中的所有标签与源节点或目标节点关联。

字符串

'A'

将该标签与源节点或目标节点关联。

布尔值

true

关联源节点或目标节点的所有标签;与 labels(s) 相同。

布尔值

false

不为源节点或目标节点加载任何标签信息;与缺少 nodeLabels 时相同。

relationshipType 的值必须是 String

表 11. relationshipType 的值
type 示例 description(描述)

字符串

type(r)'A'

将该类型与关系关联。

关系方向

原生投影支持为每个关系类型指定方向。Cypher 聚合默认会将关系查询返回的每个关系视为 NATURAL(自然)方向。

反向关系

可以通过交换源节点和目标节点来反转关系的方向。

投影 PersonBook 节点以及 KNOWSREAD 关系
MATCH (source)-[r:KNOWS|READ]->(target)
WHERE source:Book OR source:Person
WITH gds.graph.project(
  'graphWithReverseRelationships',
  target,
  source
) as g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 12. 结果
graph(图) 节点 rels

"graphWithReverseRelationships"

5

6

无向关系

通过指定 undirectedRelationshipTypes 参数,可以将关系投影为无向关系。

投影 PersonBook 节点以及 KNOWSREAD 关系
MATCH (source)-[r:KNOWS|READ]->(target)
WHERE source:Book OR source:Person
WITH gds.graph.project(
  'graphWithUndirectedRelationships',
  source,
  target,
  {},
  {undirectedRelationshipTypes: ['*']}
) as g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 13. 结果
graph(图) 节点 rels

"graphWithUndirectedRelationships"

5

12

添加自然关系和反向关系

要同时添加具有自然方向和反向方向的关系,可以在子查询中使用 UNION 子句。

投影 Person 节点以及 KNOWSKNOWN_BY 关系
MATCH (source:Person)-[:KNOWS]->(target:Person)
CALL (source, target) {
  RETURN id(source) AS sourceId, id(target) AS targetId, 'KNOWS' AS rType
  UNION
  WITH source, target
  RETURN id(target) AS sourceId, id(source) AS targetId, 'KNOWN_BY' AS rType
}
WITH gds.graph.project(
  'graphWithNaturalAndReverseRelationships',
  sourceId,
  targetId,
  {
    sourceNodeLabels: 'Person',
    targetNodeLabels: 'Person',
    relationshipType: rType
  }
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 14. 结果
graph(图) 节点 rels

"graphWithNaturalAndReverseRelationships"

3

4

节点属性

要加载节点属性,我们为源节点和目标节点添加一个包含所有属性的映射。因此,如果节点没有该属性,我们使用 Cypher 函数 coalesce() 来指定默认值。

源节点的属性在第四个 dataConfig 参数中指定为 sourceNodeProperties 键。目标节点的属性在第四个 dataConfig 参数中指定为 targetNodeProperties 键。

投影 PersonBook 节点以及 KNOWSREAD 关系
MATCH (source)-[r:KNOWS|READ]->(target)
WHERE source:Book OR source:Person
WITH gds.graph.project(
  'graphWithProperties',
  source,
  target,
  {
    sourceNodeProperties: source { age: coalesce(source.age, 18), price: coalesce(source.price, 5.0), .ratings },
    targetNodeProperties: target { age: coalesce(target.age, 18), price: coalesce(target.price, 5.0), .ratings }
  }
) as g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 15. 结果
graph(图) 节点 rels

"graphWithProperties"

5

6

投影后的 graphWithProperties 图包含五个节点和六个关系。在 Cypher 聚合中,每个节点都将获得相同的属性,这意味着不能拥有节点特定的属性。例如,在上面的示例中,Person 节点也将获得 ratingsprice 属性,而 Book 节点获得 age 属性。

此外,price 属性的默认值为 5.0。示例图中并非每本书都有指定的价格。接下来我们检查价格是否正确投影:

在投影图中验证 Adam 的 ratings 属性
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', n, 'price') AS price
ORDER BY price
表 16. 结果
名称 (name) 价格

"The Hobbit"

5.0

"Frankenstein"

19.99

我们可以看到,价格已成功投影,其中《霍比特人》具有默认价格 5.0。

关系属性

与节点属性类似,我们可以使用第四个参数投影关系属性。

投影 PersonBook 节点,以及带有 numberOfPages 属性的 READ 关系
MATCH (source)-[r:READ]->(target)
WITH gds.graph.project(
  'readWithProperties',
  source,
  target,
  { relationshipProperties: r { .numberOfPages } }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 17. 结果
graph(图) 节点 rels

"readWithProperties"

5

4

接下来,我们将验证关系属性 numberOfPages 是否已正确加载。

从投影图中流式传输关系属性 numberOfPages
CALL gds.graph.relationshipProperty.stream('readWithProperties', 'numberOfPages')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfPages
RETURN
  gds.util.asNode(sourceNodeId).name AS person,
  gds.util.asNode(targetNodeId).name AS book,
  numberOfPages
ORDER BY person ASC, numberOfPages DESC
表 18. 结果
person book numberOfPages

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

"Veselin"

"Frankenstein"

NaN

我们可以看到,numberOfPages 已被加载。默认属性值为 Double.Nan,并且可以通过使用 Cypher 函数 coalesce() 按照前面的示例 节点属性 进行更改。

并行关系

Neo4j 中的属性图模型支持并行关系,即两个节点之间的多重关系。默认情况下,GDS 保留这些并行关系。对于某些算法,我们希望投影图中两个节点之间最多包含一个关系。

实现关系去重最简单的方法是在关系查询中使用 DISTINCT 运算符。或者,我们可以通过使用 count() 函数聚合并行关系,并将计数存储为关系属性。

投影 PersonBook 节点以及 COUNT 聚合的 READ 关系
MATCH (source)-[r:READ]->(target)
WITH source, target, count(r) AS numberOfReads
WITH gds.graph.project('readCount', source, target, { relationshipProperties: { numberOfReads: numberOfReads } }) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 19. 结果
graph(图) 节点 rels

"readCount"

5

3

接下来,我们将验证 READ 关系是否已正确聚合。

流式传输投影图中 numberOfReads 的关系属性
CALL gds.graph.relationshipProperty.stream('readCount', 'numberOfReads')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfReads
RETURN
  gds.util.asNode(sourceNodeId).name AS person,
  gds.util.asNode(targetNodeId).name AS book,
  numberOfReads
ORDER BY numberOfReads DESC, person
表 20. 结果
person book numberOfReads

"Florentin"

"The Hobbit"

2.0

"Adam"

"The Hobbit"

1.0

"Veselin"

"Frankenstein"

1.0

我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系产生了 2 个 numberOfReads。

带属性的平行关系

对于具有关系属性的图,我们还可以使用 Cypher 手册中记录的其他聚合函数。

投影 PersonBook 节点,并通过对 numberOfPages 求和来聚合 READ 关系
MATCH (source)-[r:READ]->(target)
WITH source, target, sum(r.numberOfPages) AS numberOfPages
WITH gds.graph.project('readSums', source, target, { relationshipProperties: { numberOfPages: numberOfPages } }) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 21. 结果
graph(图) 节点 rels

"readSums"

5

3

接下来,我们将验证关系属性 numberOfPages 是否已正确聚合。

流式传输投影图中 numberOfPages 的关系属性
CALL gds.graph.relationshipProperty.stream('readSums', 'numberOfPages')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfPages
RETURN
  gds.util.asNode(sourceNodeId).name AS person,
  gds.util.asNode(targetNodeId).name AS book,
  numberOfPages
ORDER BY numberOfPages DESC, person
表 22. 结果
person book numberOfPages

"Florentin"

"The Hobbit"

46.0

"Adam"

"The Hobbit"

30.0

"Veselin"

"Frankenstein"

0.0

我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系总计为 46 个 numberOfPages。

投影已过滤的 Neo4j 图

Cypher 投影允许我们以更细粒度的方式指定要投影的图。以下示例将演示如何过滤掉没有 numberOfPages 属性的 READ 关系。

投影 PersonBook 节点,以及存在 numberOfPagesREAD 关系
MATCH (source) OPTIONAL MATCH (source)-[r:READ]->(target)
WHERE r.numberOfPages IS NOT NULL
WITH gds.graph.project('existingNumberOfPages', source, target, { relationshipProperties: r { .numberOfPages } }) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels
表 23. 结果
graph(图) 节点 rels

"existingNumberOfPages"

5

3

接下来,我们将验证关系属性 numberOfPages 是否已正确加载。

从投影图中流式传输关系属性 numberOfPages
CALL gds.graph.relationshipProperty.stream('existingNumberOfPages', 'numberOfPages')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfPages
RETURN
  gds.util.asNode(sourceNodeId).name AS person,
  gds.util.asNode(targetNodeId).name AS book,
  numberOfPages
ORDER BY person ASC, numberOfPages DESC
表 24. 结果
person book numberOfPages

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

如果我们将结果与关系属性中的结果进行比较,可以看到使用 IS NOT NULL 过滤掉了从 Veselin 到《弗兰肯斯坦》这本书的关系。这种功能只能通过投影子图原生投影来实现。