Cypher 投影(已弃用)
本页描述的是已弃用的旧版 Cypher 投影。替代方案是使用新的 Cypher 投影,其描述见使用 Cypher 投影图。迁移指南可在附录 C,从旧版 Cypher 投影迁移到新版找到。 |
与原生投影相比,旧版 Cypher 投影是一种更灵活、更具表达力的方法。旧版 Cypher 投影使用 Cypher 从 Neo4j 数据库创建(投影)一个内存图。
注意事项
生命周期
投影图将驻留在目录中,直到:
|
节点属性支持
旧版 Cypher 投影只能从 Cypher 查询中投影有限的节点属性类型。节点属性页面详细介绍了支持的节点属性类型。其他类型的节点属性必须转换为或编码成受支持的类型,才能使用旧版 Cypher 投影。
语法
旧版 Cypher 投影接受三个强制性参数:graphName
、nodeQuery
和 relationshipQuery
。此外,可选的 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
名称 | 可选 | 描述 |
---|---|---|
graphName |
否 |
图在目录中存储的名称。 |
nodeQuery |
否 |
用于投影节点的 Cypher 查询。查询结果必须包含一个 |
relationshipQuery |
否 |
用于投影关系的 Cypher 查询。查询结果必须包含 |
configuration |
是 |
配置旧版 Cypher 投影的其他参数。 |
名称 | 类型 | 默认 | 描述 |
---|---|---|---|
readConcurrency |
整数 |
4 |
用于创建图的并发线程数。 |
validateRelationships |
布尔值 |
true |
如果 |
parameters |
映射 |
{} |
传递给节点和关系查询的用户定义查询参数的映射。 |
jobId |
字符串 |
内部生成 |
一个可以提供以更轻松地跟踪投影进度的 ID。 |
名称 | 类型 | 描述 |
---|---|---|
graphName |
字符串 |
图在目录中存储的名称。 |
nodeQuery |
字符串 |
用于投影图中节点的 Cypher 查询。 |
nodeCount |
整数 |
投影图中存储的节点数量。 |
relationshipQuery |
字符串 |
用于投影图中关系的 Cypher 查询。 |
relationshipCount |
整数 |
投影图中存储的关系数量。 |
projectMillis |
整数 |
投影图所需毫秒数。 |
要获取有关存储图的信息(例如其 schema),可以使用gds.graph.list。 |
示例
以下所有示例都应在空数据库中运行。 |
为了演示 GDS 图投影功能,我们将创建一个小型社交网络图在 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
图 | nodeQuery | 节点 | relationshipQuery | 关系 |
---|---|---|---|---|
"persons" |
|
3 |
"MATCH (n:Person)-[r:KNOWS]→(m:Person) RETURN id(n) AS source, id(m) AS target" |
|
多图
多图是具有多个节点标签和关系类型的图。
为了在加载多个节点标签和关系类型时保留标签和类型信息,我们可以向节点查询添加一个 labels
列,并向关系查询添加一个 type
列。
Person
和 Book
节点以及 KNOWS
和 READ
关系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
图 | nodeQuery | 节点 | 关系 |
---|---|---|---|
"personsAndBooks" |
|
|
|
关系方向
原生投影支持为每个关系类型指定一个方向。旧版 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
方向投影图。
某些算法要求图以 |
节点属性
要加载节点属性,我们为每个属性向节点查询的结果添加一列。因此,我们使用 Cypher 函数 coalesce() 来指定默认值,如果节点没有该属性。
Person
和 Book
节点以及 KNOWS
和 READ
关系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
graphName | 节点 | 关系 |
---|---|---|
"graphWithProperties" |
5 |
6 |
投影的 graphWithProperties
图包含五个节点和六个关系。在旧版 Cypher 投影中,nodeQuery
中的每个节点都获得相同的节点属性,这意味着您不能拥有特定于标签的属性。例如,在上面的示例中,Person
节点也将获得 ratings
和 price
属性,而 Book
节点获得 age
属性。
此外,price
属性的默认值为 5.0
。并非每个书籍在示例图中都指定了价格。接下来我们检查价格是否正确投影。
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', id(n), 'price') AS price
ORDER BY price
姓名 | 价格 |
---|---|
"The Hobbit" |
5.0 |
"Frankenstein" |
19.99 |
我们可以看到,价格被投影,霍比特人拥有默认价格 5.0。
关系属性
与节点属性类似,我们可以使用 relationshipQuery
投影关系属性。
Person
和 Book
节点以及具有 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | 页数 |
---|---|---|
"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
关系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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | 读取次数 |
---|---|---|
"Florentin" |
"The Hobbit" |
2.0 |
"Adam" |
"The Hobbit" |
1.0 |
"Veselin" |
"Frankenstein" |
1.0 |
我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系导致 2
次读取。
具有属性的并行关系
对于具有关系属性的图,我们还可以使用 Cypher 手册中记录的其他聚合。
Person
和 Book
节点,并通过求和 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | 页数 |
---|---|---|
"Florentin" |
"The Hobbit" |
46.0 |
"Adam" |
"The Hobbit" |
30.0 |
"Veselin" |
"Frankenstein" |
0.0 |
我们可以看到,Florentin 和《霍比特人》之间的两个 READ
关系的 numberOfPages
总和为 46
。
投影过滤后的 Neo4j 图
Cypher 投影允许我们以更精细的方式指定要投影的图。以下示例将演示如何过滤掉没有 numberOfPages
属性的 READ
关系。
Person
和 Book
节点以及存在 numberOfPages
的 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 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
图 | 节点 | 关系 |
---|---|---|
"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
人物 | 书籍 | 页数 |
---|---|---|
"Adam" |
"The Hobbit" |
30.0 |
"Florentin" |
"The Hobbit" |
42.0 |
"Florentin" |
"The Hobbit" |
4.0 |
使用查询参数
与 Cypher 类似,也可以设置查询参数。在以下示例中,我们提供一个字符串列表来限制要投影的城市。
Person
和 Book
节点以及 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
图 | 节点 | 关系 |
---|---|---|
"existingNumberOfPages" |
5 |
2 |
参数的进一步用法
这些参数也可以直接用于传入节点列表或关系列表。例如,如果节点过滤器开销很大,预先计算节点列表会很有用。
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
graphName | 节点 | 关系 |
---|---|---|
"personSubset" |
2 |
1 |
通过将相关人员作为参数传递,上述查询可以转换为以下形式
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
graphName | 节点 | 关系 |
---|---|---|
"personSubsetViaParameters" |
2 |
1 |