Cypher 投影
注意事项
生命周期
投影后的图驻留在内存中(在图目录中),直到发生以下任一情况:
-
使用
gds.graph.drop过程删除了该图。 -
投影该图的 Neo4j 数据库被停止或删除。
-
Neo4j DBMS 被停止。
节点属性支持
Cypher 投影只能从 Cypher 查询中投影一组有限的节点属性类型。节点属性页面详细说明了支持哪些节点属性类型。其他类型的节点属性必须转换为受支持的类型或编码为受支持的类型,才能使用 Cypher 投影进行投影。
节点属性和标签的选择
如果一个节点出现多次,则投影将使用第一次出现时的节点属性和标签。当一个节点既可以是源节点也可以是目标节点,且它们的配置不同时,这一点非常重要。相关的配置选项有 sourceNodeProperties、targetNodeProperties、sourceNodeLabels 和 targetNodeLabels。
并行 Cypher 运行时
Cypher 投影与并行运行时兼容,可用于加快投影执行速度。所实现的加速取决于查询的可并行化程度。请注意,并行运行时仅在 Neo4j 企业版中提供,且从 5.13 版本开始提供。
语法
Cypher 投影是对正在投影的关系的聚合函数;因此,它返回一个包含投影图相关信息的对象。
投影函数接受两个强制参数:graphName 和 sourceNode。第三个参数是 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
| 名称 | 可选 | 描述 |
|---|---|---|
graphName |
否 |
图在目录中存储时所使用的名称。 |
sourceNode |
否 |
关系的源节点。不能为空。 |
targetNode |
是 |
关系的目标节点。targetNode 可以为 null(例如由于 |
是 |
源节点和目标节点的属性与标签配置,以及关系的属性和类型配置。 |
|
是 |
用于配置投影的附加参数。 |
| 名称 | 类型 | 默认 | 描述 |
|---|---|---|---|
sourceNodeProperties |
Map |
{} |
源节点的属性。 |
targetNodeProperties |
Map |
{} |
目标节点的属性。 |
sourceNodeLabels(源节点标签) |
字符串列表或字符串 |
[] |
源节点的标签。 |
targetNodeLabels(目标节点标签) |
字符串列表或字符串 |
[] |
目标节点的标签。 |
relationshipProperties |
Map |
{} |
关系的属性。 |
关系类型 (relationshipType) |
字符串 |
'*' |
关系的类型。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
readConcurrency |
整数 |
4 [1] |
是 |
创建图时使用的并发线程数。 |
undirectedRelationshipTypes |
字符串列表 |
[] |
是 |
将多个关系类型声明为无向。具有指定类型的关系将作为无向关系导入。可以使用 |
inverseIndexedRelationshipTypes |
字符串列表 |
[] |
是 |
声明多个也将进行反向索引的关系类型。可以使用 |
内存 Aura 图分析 |
字符串 |
- |
否 [2] |
声明为投影图创建的 GDS 会话所使用的内存。 |
TTL Aura 图分析 |
Duration |
PT1H |
是 |
声明为投影图创建的 GDS 会话因不活动而过期之前的生存时间。 |
sessionId Aura 图分析 |
字符串 |
- |
是 [2] |
应该在其上投影图形的 GDS 会话的 ID。 |
batchSize Aura 图分析 |
整数 |
10000 |
是 |
从 DBMS 传输到会话的批处理大小。降低该值可减少 DBMS 端的内存使用。增加该值可减少发送到 GDS 会话的批次数。 |
| 名称 | 类型 | 描述 |
|---|---|---|
graphName |
字符串 |
图在目录中存储时所使用的名称。 |
nodeCount |
整数 |
投影图中存储的节点数。 |
relationshipCount |
整数 |
投影图中存储的关系数。 |
projectMillis |
整数 |
投影图所耗费的毫秒数。 |
query |
字符串 |
此投影使用的查询。 |
配置 |
整数 |
此投影使用的配置。 |
| 要获取已存储图的信息(例如其模式),可以使用 gds.graph.list。 |
示例
|
以下所有示例应在空数据库中运行。 |
为了演示 GDS Cypher 聚合,我们将创建一个小型社交网络图。示例图如下所示
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
| graph(图) | 节点 | rels |
|---|---|---|
"persons" |
3 |
|
带有孤立节点的图
为了投影未连接的节点,我们可以使用 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
| graph(图) | 节点 | rels |
|---|---|---|
"persons" |
5 |
|
使用并行运行时
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
| graph(图) | 节点 | rels |
|---|---|---|
"persons" |
3 |
|
任意源和目标 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
| graph(图) | 节点 | rels |
|---|---|---|
"arbitrary" |
5 |
|
|
投影图无法再将投影节点连接到基础数据库中的现有节点。因此,无法在此图上执行 |
多图
多重图是具有多个节点标签和关系类型的图。
为了在加载多个节点标签时保留标签,我们可以将 sourceNodeLabels 键和 targetNodeLabels 键添加到第四个 dataConfig 参数中。— 为了在加载多个关系类型时保留类型信息,我们可以将 relationshipType 键添加到第四个 dataConfig 参数中。
Person 和 Book 节点以及 KNOWS 和 READ 关系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
| graph(图) | 节点 | rels |
|---|---|---|
"personsAndBooks" |
|
|
sourceNodeLabels 或 targetNodeLabels 的值可以是以下之一
| type | 示例 | description(描述) |
|---|---|---|
字符串列表 |
|
将该列表中的所有标签与源节点或目标节点关联。 |
字符串 |
|
将该标签与源节点或目标节点关联。 |
布尔值 |
|
关联源节点或目标节点的所有标签;与 |
布尔值 |
|
不为源节点或目标节点加载任何标签信息;与缺少 |
relationshipType 的值必须是 String
| type | 示例 | description(描述) |
|---|---|---|
字符串 |
|
将该类型与关系关联。 |
关系方向
原生投影支持为每个关系类型指定方向。Cypher 聚合默认会将关系查询返回的每个关系视为 NATURAL(自然)方向。
反向关系
可以通过交换源节点和目标节点来反转关系的方向。
Person 和 Book 节点以及 KNOWS 和 READ 关系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
| graph(图) | 节点 | rels |
|---|---|---|
"graphWithReverseRelationships" |
5 |
6 |
无向关系
通过指定 undirectedRelationshipTypes 参数,可以将关系投影为无向关系。
Person 和 Book 节点以及 KNOWS 和 READ 关系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
| graph(图) | 节点 | rels |
|---|---|---|
"graphWithUndirectedRelationships" |
5 |
12 |
添加自然关系和反向关系
要同时添加具有自然方向和反向方向的关系,可以在子查询中使用 UNION 子句。
Person 节点以及 KNOWS 和 KNOWN_BY 关系MATCH (source:Person)-[:KNOWS]->(target:Person)
CALL (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
| graph(图) | 节点 | rels |
|---|---|---|
"graphWithNaturalAndReverseRelationships" |
3 |
4 |
节点属性
要加载节点属性,我们为源节点和目标节点添加一个包含所有属性的映射。因此,如果节点没有该属性,我们使用 Cypher 函数 coalesce() 来指定默认值。
源节点的属性在第四个 dataConfig 参数中指定为 sourceNodeProperties 键。目标节点的属性在第四个 dataConfig 参数中指定为 targetNodeProperties 键。
Person 和 Book 节点以及 KNOWS 和 READ 关系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
| graph(图) | 节点 | rels |
|---|---|---|
"graphWithProperties" |
5 |
6 |
投影后的 graphWithProperties 图包含五个节点和六个关系。在 Cypher 聚合中,每个节点都将获得相同的属性,这意味着不能拥有节点特定的属性。例如,在上面的示例中,Person 节点也将获得 ratings 和 price 属性,而 Book 节点获得 age 属性。
此外,price 属性的默认值为 5.0。示例图中并非每本书都有指定的价格。接下来我们检查价格是否正确投影:
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', n, 'price') AS price
ORDER BY price
| 名称 (name) | 价格 |
|---|---|
"The Hobbit" |
5.0 |
"Frankenstein" |
19.99 |
我们可以看到,价格已成功投影,其中《霍比特人》具有默认价格 5.0。
关系属性
与节点属性类似,我们可以使用第四个参数投影关系属性。
Person 和 Book 节点,以及带有 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
| graph(图) | 节点 | rels |
|---|---|---|
"readWithProperties" |
5 |
4 |
接下来,我们将验证关系属性 numberOfPages 是否已正确加载。
numberOfPagesCALL 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
| 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
| 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
| 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
| 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
| person | book | numberOfPages |
|---|---|---|
"Florentin" |
"The Hobbit" |
46.0 |
"Adam" |
"The Hobbit" |
30.0 |
"Veselin" |
"Frankenstein" |
0.0 |
我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系总计为 46 个 numberOfPages。
投影已过滤的 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
| graph(图) | 节点 | rels |
|---|---|---|
"existingNumberOfPages" |
5 |
3 |
接下来,我们将验证关系属性 numberOfPages 是否已正确加载。
numberOfPagesCALL 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
| person | book | numberOfPages |
|---|---|---|
"Adam" |
"The Hobbit" |
30.0 |
"Florentin" |
"The Hobbit" |
42.0 |
"Florentin" |
"The Hobbit" |
4.0 |