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

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

configuration

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

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

sourceNodeProperties

Map

{}

源节点的属性。

targetNodeProperties

Map

{}

目标节点的属性。

sourceNodeLabels

字符串列表或字符串

[]

源节点的标签。

targetNodeLabels

字符串列表或字符串

[]

源节点的标签。

relationshipProperties

Map

{}

源节点的属性。

relationshipType

字符串

'*'

关系的类型。

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

readConcurrency

整数

4

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

undirectedRelationshipTypes

字符串列表

[]

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

inverseIndexedRelationshipTypes

字符串列表

[]

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

表 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. 结果
graph nodes 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 nodes 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 nodes rels

"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. 结果
graph nodes 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 nodes rels

"personsAndBooks"

5

6

sourceNodeLabelstargetNodeLabels 的值可以是以下之一

表 10. sourceNodeLabelstargetNodeLabels 的值
type example description

字符串列表

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

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

字符串

'A'

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

布尔值

true

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

布尔值

false

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

relationshipType 的值必须是 String

表 11. relationshipType 的值
type example 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 nodes 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 nodes rels

"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. 结果
graph nodes 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 nodes 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 price

"The Hobbit"

5.0

"Frankenstein"

19.99

我们可以看到,价格已正确投影,其中 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. 结果
graph nodes 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() 函数对并行关系进行聚合,并将计数存储为关系属性。

投影 `Person` 和 `Book` 节点以及 `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 nodes 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 手册 中记录的其他聚合。

投影 `Person` 和 `Book` 节点以及通过对 `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 nodes 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` 关系的 `numberOfPages` 之和为 `46`。

投影过滤后的 Neo4j 图

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

投影 `Person` 和 `Book` 节点以及存在 `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. 结果
graph nodes 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 到书籍《科学怪人》的关系。此功能只能通过投影 子图原生投影 来表达。