Cypher 投影
注意事项
节点属性支持
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 |
用于创建图的并发线程数。 |
undirectedRelationshipTypes |
字符串列表 |
[] |
将一些关系类型声明为无向。 具有指定类型的关系将作为无向关系导入。 |
inverseIndexedRelationshipTypes |
字符串列表 |
[] |
声明一些关系类型,这些关系类型也将以反向索引。 |
名称 | 类型 | 描述 |
---|---|---|
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
graph | nodes | 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 | nodes | 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 | nodes | 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
graph | nodes | 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 | nodes | rels |
---|---|---|
"personsAndBooks" |
|
|
sourceNodeLabels
或 targetNodeLabels
的值可以是以下之一
type | example | description |
---|---|---|
字符串列表 |
|
将该列表中的所有标签与源节点或目标节点关联。 |
字符串 |
|
将该标签与源节点或目标节点关联。 |
布尔值 |
|
将源节点或目标节点的所有标签关联;与 |
布尔值 |
|
不要为源节点或目标节点加载任何标签信息;与 |
relationshipType
的值必须是 String
type | example | 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 | nodes | 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 | nodes | 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
graph | nodes | 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 | nodes | 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 | price |
---|---|
"The Hobbit" |
5.0 |
"Frankenstein" |
19.99 |
我们可以看到,价格已正确投影,其中 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
graph | nodes | 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
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() 函数对并行关系进行聚合,并将计数存储为关系属性。
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 | nodes | rels |
---|---|---|
"readCount" |
5 |
3 |
接下来,我们将验证 `READ` 关系是否已正确聚合。
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 手册 中记录的其他聚合。
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 | nodes | rels |
---|---|---|
"readSums" |
5 |
3 |
接下来,我们将验证关系属性 `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` 关系的 `numberOfPages` 之和为 `46`。
投影过滤后的 Neo4j 图
Cypher 投影允许我们以更细粒度的方式指定要投影的图。以下示例将演示如何过滤掉没有 `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 | nodes | 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
person | book | numberOfPages |
---|---|---|
"Adam" |
"The Hobbit" |
30.0 |
"Florentin" |
"The Hobbit" |
42.0 |
"Florentin" |
"The Hobbit" |
4.0 |