Cypher 投影
注意事项
生命周期
投影图存在于内存中(在图目录中),直到发生以下任何一种情况:
-
使用
gds.graph.drop
过程删除图。 -
投影图所基于的 Neo4j 数据库被停止或删除。
-
Neo4j 数据库管理系统被停止。
节点属性支持
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 |
否 |
关系的源节点。不能为 null。 |
targetNode |
是 |
关系的目标节点。 |
是 |
源节点和目标节点的属性和标签配置,以及关系的属性和类型配置。 |
|
是 |
配置投影的其他参数。 |
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
sourceNodeProperties |
映射 |
{} |
源节点的属性。 |
targetNodeProperties |
映射 |
{} |
目标节点的属性。 |
sourceNodeLabels |
字符串列表或字符串 |
[] |
源节点的标签。 |
targetNodeLabels |
字符串列表或字符串 |
[] |
目标节点的标签。 |
relationshipProperties |
映射 |
{} |
关系的属性。 |
relationshipType |
字符串 |
'*' |
关系的类型。 |
名称 | 类型 | 默认值 | 可选 | 描述 |
---|---|---|---|---|
readConcurrency |
整数 |
4 [1] |
是 |
用于创建图的并发线程数。 |
undirectedRelationshipTypes |
字符串列表 |
[] |
是 |
声明一些关系类型为无向。指定类型的关系将作为无向关系导入。 |
inverseIndexedRelationshipTypes |
字符串列表 |
[] |
是 |
声明一些关系类型也将以反向索引。 |
内存 Aura 图分析无服务器 |
字符串 |
- |
否 [2] |
声明为投影图创建的 GDS 会话所使用的内存。 |
ttl Aura 图分析无服务器 |
持续时间 |
PT1H |
是 |
声明为投影图创建的 GDS 会话因不活动而过期前的存活时间。 |
batchSize Aura 图分析无服务器 |
整数 |
10000 |
是 |
从 DBMS 传输到会话的批次大小。降低该值以减少 DBMS 侧的内存使用。增加该值以向 GDS 会话发送更少的批次。 |
2. 仅适用于 Aura 图分析无服务器 |
名称 | 类型 | 描述 |
---|---|---|
graphName |
字符串 |
图在目录中存储的名称。 |
nodeCount |
整数 |
投影图中存储的节点数量。 |
relationshipCount |
整数 |
投影图中存储的关系数量。 |
projectMillis |
整数 |
投影图所用的毫秒数。 |
query |
字符串 |
此投影所使用的查询。 |
configuration |
整数 |
此投影所使用的配置。 |
要获取有关已存储图的信息(例如其模式),可以使用 gds.graph.list。 |
示例
以下所有示例都应在一个空数据库中运行。 |
为了演示 GDS 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
关系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
图 | 节点 | 关系 |
---|---|---|
"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
图 | 节点 | 关系 |
---|---|---|
"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
图 | 节点 | 关系 |
---|---|---|
"persons" |
3 |
|
任意源和目标 ID 值
到目前为止,示例展示了如何基于现有节点投影图。也可以直接传递 INTEGER 值。
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
图 | 节点 | 关系 |
---|---|---|
"arbitrary" |
5 |
|
投影图无法再将投影节点连接到基础数据库中的现有节点。因此,无法在此图上执行 |
多图
多图是具有多个节点标签和关系类型的图。
为了在加载多个节点标签时保留标签,我们可以在第四个 dataConfig
参数中添加 sourceNodeLabels
键和 targetNodeLabels
键。— 为了在加载多个关系类型时保留类型信息,我们可以在第四个 dataConfig
参数中添加 relationshipType
键。
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
图 | 节点 | 关系 |
---|---|---|
"personsAndBooks" |
|
|
sourceNodeLabels
或 targetNodeLabels
的值可以是以下之一:
类型 | 示例 | 描述 |
---|---|---|
字符串列表 |
|
将该列表中的所有标签与源节点或目标节点关联。 |
字符串 |
|
将该标签与源节点或目标节点关联。 |
布尔值 |
|
关联源节点或目标节点的所有标签;与 |
布尔值 |
|
不加载源节点或目标节点的任何标签信息;与 |
relationshipType
的值必须是 String
类型 | 示例 | 描述 |
---|---|---|
字符串 |
|
将该类型与关系关联。 |
关系方向
原生投影支持为每个关系类型指定方向。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
图 | 节点 | 关系 |
---|---|---|
"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
图 | 节点 | 关系 |
---|---|---|
"graphWithUndirectedRelationships" |
5 |
12 |
添加自然关系和反向关系
要同时添加具有其自然方向和反向方向的关系,可以在子查询中使用 UNION
子句。
Person
节点以及 KNOWS
和 KNOWN_BY
关系MATCH (source:Person)-[:KNOWS]->(target:Person)
CALL {
WITH 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
图 | 节点 | 关系 |
---|---|---|
"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
图 | 节点 | 关系 |
---|---|---|
"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
名称 | 价格 |
---|---|
"The Hobbit" |
5.0 |
"Frankenstein" |
19.99 |
我们可以看到,价格被正确投影,霍比特人(The Hobbit)的价格默认为 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | numberOfReads |
---|---|---|
"Florentin" |
"The Hobbit" |
2.0 |
"Adam" |
"The Hobbit" |
1.0 |
"Veselin" |
"Frankenstein" |
1.0 |
我们可以看到,Florentin 和 Hobbit 之间的两个 READ 关系导致 numberOfReads
为 2
。
带属性的并行关系
对于带有关系属性的图,我们还可以使用 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | numberOfPages |
---|---|---|
"Florentin" |
"The Hobbit" |
46.0 |
"Adam" |
"The Hobbit" |
30.0 |
"Veselin" |
"Frankenstein" |
0.0 |
我们可以看到,Florentin 和 Hobbit 之间的两个 READ
关系加起来的总页数为 46
。
投影过滤后的 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | numberOfPages |
---|---|---|
"Adam" |
"The Hobbit" |
30.0 |
"Florentin" |
"The Hobbit" |
42.0 |
"Florentin" |
"The Hobbit" |
4.0 |