Cypher 投影(已弃用)

此页面描述了已弃用的旧版 Cypher 投影。替换方法是使用新的 Cypher 投影,该投影在 使用 Cypher 投影图 中进行了描述。迁移指南可以在 附录 C,从旧版迁移到新的 Cypher 投影 中找到。

旧版 Cypher 投影与 原生投影 相比,是一种更灵活和更具表现力的方法。旧版 Cypher 投影使用 Cypher 从 Neo4j 数据库创建(*投影*)内存中的图。

考量因素

生命周期

投影的图将驻留在目录中,直到:

  • 使用 gds.graph.drop 删除图

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

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

节点属性支持

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

语法

旧版 Cypher 投影需要三个必填参数:graphNamenodeQueryrelationshipQuery。此外,可选的 configuration 参数允许我们进一步配置图的创建。

CALL gds.graph.project.cypher(
    graphName: String,
    nodeQuery: String,
    relationshipQuery: String,
    configuration: Map
) YIELD
    graphName: String,
    nodeQuery: String,
    nodeCount: Integer,
    relationshipQuery: String,
    relationshipCount: Integer,
    projectMillis: Integer
表 1. 参数
名称 可选 描述

graphName

图在目录中存储的名称。

nodeQuery

用于投影节点的 Cypher 查询。查询结果必须包含 id 列。可选地,可以指定 labels 列来表示节点标签。其他列被解释为属性。

relationshipQuery

用于投影关系的 Cypher 查询。查询结果必须包含 sourcetarget 列。可选地,可以指定 type 列来表示关系类型。其他列被解释为属性。

configuration

用于配置旧版 Cypher 投影的其他参数。

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

readConcurrency

整数

4

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

validateRelationships

布尔值

true

如果relationshipQuery返回的节点之间的关系不是由nodeQuery返回的,则是否抛出错误。

参数

映射

{}

传递到节点和关系查询的用户定义查询参数的映射。

jobId

字符串

内部生成

可以提供的 ID,以便更容易跟踪投影的进度。

表 3. 结果
名称 类型 描述

graphName

字符串

图在目录中存储的名称。

nodeQuery

字符串

用于投影图中节点的 Cypher 查询。

nodeCount

整数

投影图中存储的节点数。

relationshipQuery

字符串

用于投影图中关系的 Cypher 查询。

relationshipCount

整数

投影图中存储的关系数。

projectMillis

整数

投影图的毫秒数。

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

示例

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

为了演示 GDS Graph Project 的功能,我们将创建一个小型社交网络图在 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关系
CALL gds.graph.project.cypher(
  'persons',
  'MATCH (n:Person) RETURN id(n) AS id',
  'MATCH (n:Person)-[r:KNOWS]->(m:Person) RETURN id(n) AS source, id(m) AS target')
YIELD
  graphName AS graph, nodeQuery, nodeCount AS nodes, relationshipQuery, relationshipCount AS rels
表 4. 结果
graph nodeQuery nodes relationshipQuery rels

"persons"

"MATCH (n:Person) RETURN id(n) AS id"

3

"MATCH (n:Person)-[r:KNOWS]→(m:Person) RETURN id(n) AS source, id(m) AS target"

2

多图

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

当我们加载多个节点标签和关系类型时,要保留标签和类型信息,我们可以向节点查询添加labels列,向关系查询添加type列。

投影PersonBook节点以及KNOWSREAD关系
CALL gds.graph.project.cypher(
  'personsAndBooks',
  'MATCH (n) WHERE n:Person OR n:Book RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:KNOWS|READ]->(m) RETURN id(n) AS source, id(m) AS target, type(r) AS type')
YIELD
  graphName AS graph, nodeQuery, nodeCount AS nodes, relationshipCount AS rels
表 5. 结果
graph nodeQuery nodes rels

"personsAndBooks"

"MATCH (n) WHERE n:Person OR n:Book RETURN id(n) AS id, labels(n) AS labels"

5

6

关系方向

本机投影支持为每个关系类型指定方向。Legacy Cypher 投影将关系查询返回的每个关系视为NATURAL方向,并从第一个提供的 id(源)到第二个(目标)创建一个有向关系。通过切换 RETURN 子句中 id 的顺序来实现REVERSE方向的投影,例如MATCH (n)-[r:KNOWS]→(m) RETURN id(m) AS source, id(n) AS target, type(r) AS type

当使用 Legacy Cypher 投影时,无法以UNDIRECTED方向投影图。

某些算法要求图以UNDIRECTED方向加载。这些算法不能用于由 Legacy Cypher 投影投影的图。

节点属性

要加载节点属性,我们将为每个属性向节点查询的结果添加一列。因此,我们使用 Cypher 函数coalesce()函数来指定默认值,如果节点没有该属性。

投影PersonBook节点以及KNOWSREAD关系
CALL gds.graph.project.cypher(
  'graphWithProperties',
  'MATCH (n)
   WHERE n:Book OR n:Person
   RETURN
    id(n) AS id,
    labels(n) AS labels,
    coalesce(n.age, 18) AS age,
    coalesce(n.price, 5.0) AS price,
    n.ratings AS ratings',
  'MATCH (n)-[r:KNOWS|READ]->(m) RETURN id(n) AS source, id(m) AS target, type(r) AS type'
)
YIELD
  graphName, nodeCount AS nodes, relationshipCount AS rels
RETURN graphName, nodes, rels
表 6. 结果
graphName nodes rels

"graphWithProperties"

5

6

投影的graphWithProperties图包含五个节点和六个关系。在 Legacy Cypher 投影中,来自nodeQuery的每个节点都获得相同的节点属性,这意味着您无法拥有特定于标签的属性。例如,在上面的示例中,Person节点也将获得ratingsprice属性,而Book节点将获得age属性。

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

验证投影图中 Adam 的评分属性
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', id(n), 'price') AS price
ORDER BY price
表 7. 结果
name price

"The Hobbit"

5.0

"Frankenstein"

19.99

我们可以看到,价格是投影的,其中霍比特人的默认价格为 5.0。

关系属性

与节点属性类似,我们可以使用relationshipQuery来投影关系属性。

投影PersonBook节点以及具有numberOfPages属性的READ关系
CALL gds.graph.project.cypher(
  'readWithProperties',
  'MATCH (n) RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:READ]->(m)
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, r.numberOfPages AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
表 8. 结果
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
表 9. 结果
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关系
CALL gds.graph.project.cypher(
  'readCount',
  'MATCH (n) RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:READ]->(m)
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, count(r) AS numberOfReads'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
表 10. 结果
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
表 11. 结果
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关系
CALL gds.graph.project.cypher(
  'readSums',
  'MATCH (n) RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:READ]->(m)
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, sum(r.numberOfPages) AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
表 12. 结果
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
表 13. 结果
person book numberOfPages

"Florentin"

"The Hobbit"

46.0

"Adam"

"The Hobbit"

30.0

"Veselin"

"Frankenstein"

0.0

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

投影过滤的 Neo4j 图

Cypher 投影使我们能够以更细粒度的方式指定要投影的图。以下示例将演示如何过滤掉READ关系,前提是它们没有numberOfPages属性。

投影PersonBook节点以及存在numberOfPagesREAD关系
CALL gds.graph.project.cypher(
  'existingNumberOfPages',
  'MATCH (n) RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:READ]->(m)
    WHERE r.numberOfPages IS NOT NULL
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, r.numberOfPages AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
表 14. 结果
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
表 15. 结果
person book numberOfPages

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

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

使用查询参数

Cypher类似,也可以设置查询参数。在以下示例中,我们提供字符串列表来限制要投影的城市。

投影PersonBook节点以及numberOfPages大于 9 的READ关系
CALL gds.graph.project.cypher(
  'existingNumberOfPages',
  'MATCH (n) RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:READ]->(m)
    WHERE r.numberOfPages > $minNumberOfPages
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, r.numberOfPages AS numberOfPages',
  { parameters: { minNumberOfPages: 9} }
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
表 16. 结果
graph nodes rels

"existingNumberOfPages"

5

2

参数的进一步使用

参数也可以用于直接传递节点列表或关系列表。例如,如果节点过滤器很昂贵,则预先计算节点列表可能很有用。

投影年龄小于 17 且姓名不以V开头的Person节点,以及KNOWS关系
CALL gds.graph.project.cypher(
  'personSubset',
  'MATCH (n)
    WHERE n.age < 20 AND NOT n.name STARTS WITH "V"
    RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:KNOWS]->(m)
    WHERE (n.age < 20 AND NOT n.name STARTS WITH "V") AND
          (m.age < 20 AND NOT m.name STARTS WITH "V")
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, r.numberOfPages AS numberOfPages'
)
YIELD
  graphName, nodeCount AS nodes, relationshipCount AS rels
表 17. 结果
graphName nodes rels

"personSubset"

2

1

通过将相关的 Persons 作为参数传递,上面的查询可以转换为以下查询

通过使用参数来投影年龄小于 20 且姓名不以V开头的Person节点,以及KNOWS关系
MATCH (n)
WHERE n.age < 20 AND NOT n.name STARTS WITH "V"
WITH collect(n) AS olderPersons
CALL gds.graph.project.cypher(
  'personSubsetViaParameters',
  'UNWIND $nodes AS n RETURN id(n) AS id, labels(n) AS labels',
  'MATCH (n)-[r:KNOWS]->(m)
    WHERE (n IN $nodes) AND (m IN $nodes)
    RETURN id(n) AS source, id(m) AS target, type(r) AS type, r.numberOfPages AS numberOfPages',
  { parameters: { nodes: olderPersons} }
)
 YIELD
  graphName, nodeCount AS nodes, relationshipCount AS rels
 RETURN graphName, nodes, rels
表 18. 结果
graphName nodes rels

"personSubsetViaParameters"

2

1