从旧版到新的 Cypher 投影迁移

谁应该阅读本指南

本指南适用于一直使用旧版 Cypher 投影 gds.graph.project.cypher 的用户。Cypher 投影现在使用 gds.graph.project 聚合函数完成。我们假设大多数提到的操作和概念可以理解,无需过多解释。因此,我们在示例和比较中有意简明扼要。有关详细信息,请参阅 Cypher 投影的文档

结构性变化

旧版 Cypher 投影是一个独立的过程调用,其中 Cypher 查询作为字符串参数传递并由 GDS 执行。新的 Cypher 投影是一个聚合函数,作为 Cypher 查询的一部分调用。GDS 不再负责或控制 Cypher 查询的执行。迁移到新的 Cypher 投影需要更改整个 Cypher 查询的编写方式。

节点和关系不再有单独的查询。相反,编写一个查询,该查询生成源节点和目标节点对,并使用 gds.graph.project 将其聚合到图目录中。由于旧版 Cypher 投影中的关系查询已经要求返回源节点和目标节点对,因此它是新查询的一个很好的起点。粗略地说,查询必须按如下方式重写

表 1. 两种 Cypher 投影之间的结构性变化
旧版 新版
CALL gds.graph.project.cypher(
  $graphName,
  $nodeQuery,
  $relationshipQuery,
  $configuration
)
$relationshipQuery
RETURN gds.graph.project(
  $graphName,
  sourceNode,
  targetNode,
  $dataConfig,
  $configuration
)

查询不再需要遵守特定结构,您可以使用任何生成源节点和目标节点对的 Cypher 查询。

语义变化

旧版 Cypher 投影对节点和关系有单独的查询。节点查询首先执行,并定义图中的所有节点。关系查询其次执行,先前导入的节点作为关系的过滤器。仅先前导入的节点之间的关系才会被导入到图中。作为节点查询的一部分导入的任何节点,如果未出现在任何关系中,则会导致图中的一个断开连接的节点。默认情况下,所有节点都是断开连接的,除非它们也出现在关系中。

新的 Cypher 投影不再对节点和关系进行单独查询。节点查询不再需要,节点会从源节点和目标节点对中隐式创建。断开的节点必须通过在目标节点的位置提供 `NULL` 来在查询中显式创建。默认情况下,所有节点都连接,除非它们被显式断开连接。

由于新的 Cypher 投影不再负责执行 Cypher 查询,因此图形配置不再能返回节点和关系查询。

示例

以下示例基于 旧版 Cypher 投影新的 Cypher 投影 文档中列出的示例。

简单图形

表 2. 两种 Cypher 投影的并排比较
旧版 新版

: 具有潜在断开连接节点的简单图形投影

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, nodeCount AS nodes, relationshipCount AS rels
MATCH (n:Person)
OPTIONAL MATCH (n)-[r:KNOWS]->(m:Person)
WITH gds.graph.project('persons', n, m) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS node, g.relationshipCount AS rels

: 没有断开连接节点的简单图形投影

不适用,旧版 Cypher 投影无法保证连接节点。

MATCH (n:Person)-[r:KNOWS]->(m:Person)
WITH gds.graph.project('persons', n, m) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS node, g.relationshipCount AS rels

直接翻译需要使用 `OPTIONAL MATCH` 子句来创建断开的节点,以便创建相同的图形。这可能不是你最初想要的,但由于旧版 Cypher 投影无法保证连接节点,因此是必需的。通过使用等效于 `$relationshipQuery` 的方法,我们现在在新的 Cypher 投影中也只获得连接的节点。

另一个区别是,我们将节点直接传递给新的 Cypher 投影。旧版 Cypher 投影要求我们传递节点 ID。通过直接传递节点,Cypher 投影知道投影的源是 Neo4j 数据库,并且它允许使用 `.write` 过程。也可以传递节点 ID 而不是节点 `…​ gds.graph.project('persons', id(n), id(m))`,但仅当投影的源不是 neo4j 数据库时才建议这样做。有关更多详细信息,请参见 任意源和目标 ID 值

多重图形

表 3. 两种 Cypher 投影的并排比较
旧版 新版

: 多重图形投影

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
MATCH (n)
WHERE n:Person OR n:Book
OPTIONAL MATCH (n)-[r:KNOWS|READ]->(m)
WHERE m:Person OR m:Book
WITH gds.graph.project(
  'personsAndBooks',
  n,
  m,
  {
    sourceNodeLabels: labels(n),
    targetNodeLabels: labels(m),
    relationshipType: type(r)
  }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS node, g.relationshipCount AS rels

与前面的示例类似,我们必须使用 `OPTIONAL MATCH` 子句来创建断开的节点,以便创建相同的图形。根据实际的图形模式以及是否需要断开的节点,查询的外观也会有所不同。

节点标签和关系类型作为额外的配置映射传递给新的 Cypher 投影。节点标签需要作为 `sourceNodeLabels` 和 `targetNodeLabels` 传递,关系类型需要作为 `relationshipType` 传递。有关更多详细信息,请参见 多重图形

节点属性

表 4. 两种 Cypher 投影的并排比较
旧版 新版

: 带有节点属性的图形投影

CALL gds.graph.project.cypher(
  'graphWithProperties',
  'MATCH (n:Person)
   RETURN
    id(n) AS id,
    labels(n) AS labels,
    n.age AS age',
  'MATCH (n)-[r:KNOWS]->(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
MATCH (n:Person)
OPTIONAL MATCH (n)-[r:KNOWS]->(m:Person)
WITH gds.graph.project(
  'graphWithProperties',
  n,
  m,
  {
    sourceNodeLabels: labels(n),
    targetNodeLabels: labels(m),
    sourceNodeProperties: n { .age },
    targetNodeProperties: m { .age },
    relationshipType: type(r)
  }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS node, g.relationshipCount AS rels

: 带有可选节点属性的图形投影

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
MATCH (n)
WHERE n:Person OR n:Book
OPTIONAL MATCH (n)-[r:KNOWS|READ]->(m)
WHERE m:Person OR m:Book
WITH gds.graph.project(
  'graphWithProperties',
  n,
  m,
  {
    sourceNodeLabels: labels(n),
    targetNodeLabels: labels(m),
    sourceNodeProperties: n { age: coalesce(n.age, 18), price: coalesce(n.price, 5.0), .ratings },
    targetNodeProperties: n { age: coalesce(n.age, 18), price: coalesce(n.price, 5.0), .ratings },
    relationshipType: type(r)
  }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS node, g.relationshipCount AS rels

与前面的示例类似,我们在额外的映射中传递标签和属性。我们可以使用映射投影以及任何其他 Cypher 表达式来创建属性。有关更多详细信息,请参见 节点属性

关系属性

表 5. 两种 Cypher 投影的并排比较
旧版 新版

: 带有关系属性的图形投影

CALL gds.graph.project.cypher(
  'readWithProperties',
  'MATCH (n) RETURN id(n) AS id',
  'MATCH (n)-[r:READ]->(m)
    RETURN id(n) AS source, id(m) AS target, r.numberOfPages AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
MATCH (n)-[r:READ]->(m)
WITH gds.graph.project(
  'readWithProperties',
  n,
  m,
  { relationshipProperties: r { .numberOfPages } }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

与前面的示例类似,我们在额外的映射中传递属性,这里使用 `relationshipProperties` 键。我们可以使用映射投影以及任何其他 Cypher 表达式来创建属性。有关更多详细信息,请参见 关系属性

并行关系

表 6. 两种 Cypher 投影的并排比较
旧版 新版

: 带有并行关系的图形投影

CALL gds.graph.project.cypher(
  'readCount',
  'MATCH (n) RETURN id(n) AS id',
  '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
MATCH (n)-[r:READ]->(m)
WITH n, m, count(r) AS numberOfReads
WITH gds.graph.project(
  'readCount',
  n,
  m,
  {
    relationshipProperties: { numberOfReads: numberOfReads }
  }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

: 带有并行关系和关系属性的图形投影

CALL gds.graph.project.cypher(
  'readSums',
  'MATCH (n) RETURN id(n) AS id',
  'MATCH (n)-[r:READ]->(m)
    RETURN id(n) AS source, id(m) AS target, sum(r.numberOfPages) AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
MATCH (n)-[r:READ]->(m)
WITH n, m, sum(r.numberOfPages) AS numberOfPages
WITH gds.graph.project(
  'readSums',
  n,
  m,
  {
    relationshipProperties: { numberOfPages: numberOfPages }
  }
) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

与旧版 Cypher 投影类似,没有机制可以让 GDS 聚合并行关系。并行关系上的聚合在查询中通过任何适合图形模式和数据的工具完成。有关更多详细信息,请参见 并行关系

投影过滤后的图形

表 7. 两种 Cypher 投影的并排比较
旧版 新版

: 带有过滤后的图形的图形投影

CALL gds.graph.project.cypher(
  'existingNumberOfPages',
  'MATCH (n) RETURN id(n) AS id',
  'MATCH (n)-[r:READ]->(m)
    WHERE r.numberOfPages IS NOT NULL
    RETURN id(n) AS source, id(m) AS target, r.numberOfPages AS numberOfPages'
)
YIELD
  graphName AS graph, nodeCount AS nodes, relationshipCount AS rels
MATCH (n) OPTIONAL MATCH (n)-[r:READ]->(m)
WHERE r.numberOfPages IS NOT NULL
WITH gds.graph.project('existingNumberOfPages', n, m, { relationshipProperties: r { .numberOfPages } }) AS g
RETURN
  g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

与旧版 Cypher 投影类似,我们可以应用任何 Cypher 方法来过滤数据,然后将其传递给 Cypher 投影。有关更多详细信息,请参见 投影过滤后的 Neo4j 图形

投影无向图形

表 8. 两种 Cypher 投影的并排比较
旧版 新版

: 带有无向图形的图形投影

不适用,旧版 Cypher 投影无法投影无向图形。

MATCH (n)-[r:KNOWS|READ]->(m)
WHERE n:Book OR n:Person
WITH gds.graph.project(
  'graphWithUndirectedRelationships',
  source,
  target,
  {},
  {undirectedRelationshipTypes: ['*']}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

新的 Cypher 投影可以投影无向图形。有关更多详细信息,请参见 无向关系

内存估计

表 9. 两种 Cypher 投影的并排比较
旧版 新版

: 投影图形的内存估计

CALL gds.graph.project.cypher.estimate(
  '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 requiredMemory, bytesMin, bytesMax
MATCH (n:Person)-[r:KNOWS]-(m)
WITH count(n) AS nodeCount, count(r) AS relationshipCount
CALL gds.graph.project.estimate('*', '*', {
  nodeCount: nodeCount,
  relationshipCount: relationshipCount,
})
YIELD requiredMemory, bytesMin, bytesMax

由于新的 Cypher 投影不再是过程,因此也不存在 `estimate` 方法。相反,我们可以使用 `gds.graph.project.estimate` 过程 来估计图形投影的内存需求。