Cypher 投影

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

Cypher 投影主要分为两部分

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

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

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

Cypher 投影比原生投影更灵活、更具表达力。

注意事项

生命周期

投影图存在于内存中(在图目录中),直到发生以下任何一种情况:

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

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

  • Neo4j 数据库管理系统被停止。

节点属性支持

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

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

targetNode

关系的目标节点。targetNode 可以为 null(例如由于 OPTIONAL MATCH),在这种情况下,源节点将作为不连接的节点进行投影。

dataConfig

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

configuration

配置投影的其他参数。

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

sourceNodeProperties

映射

{}

源节点的属性。

targetNodeProperties

映射

{}

目标节点的属性。

sourceNodeLabels

字符串列表或字符串

[]

源节点的标签。

targetNodeLabels

字符串列表或字符串

[]

目标节点的标签。

relationshipProperties

映射

{}

关系的属性。

relationshipType

字符串

'*'

关系的类型。

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

readConcurrency

整数

4 [1]

用于创建图的并发线程数。

undirectedRelationshipTypes

字符串列表

[]

声明一些关系类型为无向。指定类型的关系将作为无向关系导入。* 可用于声明所有关系类型为无向。

inverseIndexedRelationshipTypes

字符串列表

[]

声明一些关系类型也将以反向索引。* 可用于声明所有关系类型为反向索引。

内存 Aura 图分析无服务器

字符串

-

[2]

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

ttl Aura 图分析无服务器

持续时间

PT1H

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

batchSize Aura 图分析无服务器

整数

10000

从 DBMS 传输到会话的批次大小。降低该值以减少 DBMS 侧的内存使用。增加该值以向 GDS 会话发送更少的批次。

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

2. 仅适用于 Aura 图分析无服务器

表 4. 结果
名称 类型 描述

graphName

字符串

图在目录中存储的名称。

nodeCount

整数

投影图中存储的节点数量。

relationshipCount

整数

投影图中存储的关系数量。

projectMillis

整数

投影图所用的毫秒数。

query

字符串

此投影所使用的查询。

configuration

整数

此投影所使用的配置。

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

示例

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

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

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. 结果
节点 关系

"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. 结果
节点 关系

"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. 结果
节点 关系

"persons"

3

2

任意源和目标 ID 值

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

投影任意 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. 结果
节点 关系

"arbitrary"

5

3

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

多图

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

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

投影 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. 结果
节点 关系

"personsAndBooks"

5

6

sourceNodeLabelstargetNodeLabels 的值可以是以下之一:

表 10. sourceNodeLabelstargetNodeLabels 的值
类型 示例 描述

字符串列表

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

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

字符串

'A'

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

布尔值

true

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

布尔值

false

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

relationshipType 的值必须是 String

表 11. relationshipType 的值
类型 示例 描述

字符串

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. 结果
节点 关系

"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. 结果
节点 关系

"graphWithUndirectedRelationships"

5

12

添加自然关系和反向关系

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

投影 Person 节点以及 KNOWSKNOWN_BY 关系
MATCH (source:Person)-[:KNOWS]->(target:Person)
CALL {
  WITH 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. 结果
节点 关系

"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. 结果
节点 关系

"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. 结果
名称 价格

"The Hobbit"

5.0

"Frankenstein"

19.99

我们可以看到,价格被正确投影,霍比特人(The Hobbit)的价格默认为 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. 结果
节点 关系

"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. 结果
人物 书籍 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. 结果
节点 关系

"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. 结果
人物 书籍 numberOfReads

"Florentin"

"The Hobbit"

2.0

"Adam"

"The Hobbit"

1.0

"Veselin"

"Frankenstein"

1.0

我们可以看到,Florentin 和 Hobbit 之间的两个 READ 关系导致 numberOfReads2

带属性的并行关系

对于带有关系属性的图,我们还可以使用 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. 结果
节点 关系

"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. 结果
人物 书籍 numberOfPages

"Florentin"

"The Hobbit"

46.0

"Adam"

"The Hobbit"

30.0

"Veselin"

"Frankenstein"

0.0

我们可以看到,Florentin 和 Hobbit 之间的两个 READ 关系加起来的总页数为 46

投影过滤后的 Neo4j 图

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

投影 PersonBook 节点以及存在 numberOfPages 属性的 READ 关系
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. 结果
节点 关系

"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. 结果
人物 书籍 numberOfPages

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

如果我们将结果与关系属性中的结果进行比较,我们可以看到使用 IS NOT NULL 过滤掉了 Veselin 到 Frankenstein 这本书的关系。此功能只能通过投影子图,使用原生投影来表达。

© . All rights reserved.