Cypher 投影(已弃用)

本页面介绍了已弃用的传统 Cypher 投影。替代方案是使用新的 Cypher 投影,详见 使用 Cypher 投影图。迁移指南请参考 附录 C,从传统 Cypher 投影迁移到新 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 列来表示关系类型。其他列将被解释为属性。

配置

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

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

readConcurrency

整数

4

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

validateRelationships

布尔值

true

如果 relationshipQuery 返回的节点之间存在关系,但这些节点未由 nodeQuery 返回,是否抛出错误。

parameters

Map

{}

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

jobId

字符串

内部生成

一个 ID,可以提供该 ID 以便更轻松地跟踪投影进度。

表 3. 结果
名称 类型 描述

graphName

字符串

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

nodeQuery

字符串

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

nodeCount

整数

投影图中存储的节点数。

relationshipQuery

字符串

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

relationshipCount

整数

投影图中存储的关系数。

projectMillis

整数

投影图所耗费的毫秒数。

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

示例

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

为了演示 GDS 图投影的功能,我们将在 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 节点 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 节点 rels

"personsAndBooks"

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

5

6

关系方向

原生投影支持为每种关系类型指定方向。传统 Cypher 投影将关系查询返回的每一条关系视为 NATURAL(自然)方向,并从第一个提供的 ID(源)到第二个 ID(目标)创建有向关系。通过在 RETURN 子句中交换 ID 的顺序(例如 MATCH (n)-[r:KNOWS]→(m) RETURN id(m) AS source, id(n) AS target, type(r) AS type),可以实现 REVERSE(反向)方向的投影。

使用传统 Cypher 投影时,无法以 UNDIRECTED(无向)方向投影图。

某些算法要求图以 UNDIRECTED 方向加载。这些算法无法与由传统 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 节点 rels

"graphWithProperties"

5

6

投影的 graphWithProperties 图包含 5 个节点和 6 条关系。在传统 Cypher 投影中,nodeQuery 中的每个节点都会获得相同的节点属性,这意味着不能拥有标签特定的属性。例如,在上面的示例中,Person 节点也会获得 ratingsprice 属性,而 Book 节点会获得 age 属性。

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

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

"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(图) 节点 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 聚合的 READ 关系
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(图) 节点 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 和 the Hobbit 之间的两条 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(图) 节点 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 和 the Hobbit 之间的两条 READ 关系总计为 46 个 numberOfPages。

投影过滤后的 Neo4j 图

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

投影 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(图) 节点 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(图) 节点 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 节点 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 节点 rels

"personSubsetViaParameters"

2

1