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 返回的节点中,是否抛出错误。

parameters

映射

{}

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

jobId

字符串

内部生成

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

表 3. 结果
名称 类型 描述

graphName

字符串

图在目录中存储的名称。

nodeQuery

字符串

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

nodeCount

整数

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

relationshipQuery

字符串

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

relationshipCount

整数

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

projectMillis

整数

投影图所需毫秒数。

要获取有关存储图的信息(例如其 schema),可以使用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. 结果
nodeQuery 节点 relationshipQuery 关系

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

"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 的顺序可以实现 REVERSE 方向投影,例如 MATCH (n)-[r:KNOWS]→(m) RETURN id(m) AS source, id(n) AS target, type(r) AS type

使用旧版 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 节点 关系

"graphWithProperties"

5

6

投影的 graphWithProperties 图包含五个节点和六个关系。在旧版 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. 结果
姓名 价格

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

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

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

"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. 结果
人物 书籍 读取次数

"Florentin"

"The Hobbit"

2.0

"Adam"

"The Hobbit"

1.0

"Veselin"

"Frankenstein"

1.0

我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系导致 2 次读取。

具有属性的并行关系

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

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

"Florentin"

"The Hobbit"

46.0

"Adam"

"The Hobbit"

30.0

"Veselin"

"Frankenstein"

0.0

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

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

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

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

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

使用查询参数

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

投影年龄小于 9 且页数大于 9 的 PersonBook 节点以及 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. 结果
节点 关系

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

"personSubset"

2

1

通过将相关人员作为参数传递,上述查询可以转换为以下形式

通过使用参数,投影年龄小于 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 节点 关系

"personSubsetViaParameters"

2

1

© . All rights reserved.