CALL 子查询
示例图
以下示例使用具有以下模式的图
要重新创建图,请在空的 Neo4j 数据库中运行以下查询
CREATE (teamA:Team {name: 'Team A'}),
(teamB:Team {name: 'Team B'}),
(teamC:Team {name: 'Team C'}),
(playerA:Player {name: 'Player A', age: 21}),
(playerB:Player {name: 'Player B', age: 23}),
(playerC:Player {name: 'Player C', age: 19}),
(playerD:Player {name: 'Player D', age: 30}),
(playerE:Player {name: 'Player E', age: 25}),
(playerF:Player {name: 'Player F', age: 35}),
(playerA)-[:PLAYS_FOR]->(teamA),
(playerB)-[:PLAYS_FOR]->(teamA),
(playerD)-[:PLAYS_FOR]->(teamB),
(playerE)-[:PLAYS_FOR]->(teamC),
(playerF)-[:PLAYS_FOR]->(teamC),
(teamA)-[:OWES {dollars: 1500}]->(teamB),
(teamA)-[:OWES {dollars: 3000}]->(teamB),
(teamB)-[:OWES {dollars: 1700}]->(teamC),
(teamC)-[:OWES {dollars: 5000}]->(teamB)
语义与性能
CALL 子查询对每个传入行执行一次。子查询中返回的变量可供包围查询的外部作用域使用。
在此示例中,CALL 子查询执行三次,每次执行对应 UNWIND 子句输出的一行。
UNWIND [0, 1, 2] AS x
CALL () {
RETURN 'hello' AS innerReturn
}
RETURN innerReturn
| innerReturn |
|---|
|
|
|
行数: 3 |
CALL 子查询的每次执行都可以观察到先前执行的变化。这允许在单个 Cypher 查询中累积结果并逐步转换数据。
在此示例中,CALL 子查询的每次迭代都会将 Player A 的 age 增加 1,并且返回的 newAge 反映了每次增量后的 age。
UNWIND [1, 2, 3] AS x
CALL () {
MATCH (p:Player {name: 'Player A'})
SET p.age = p.age + 1
RETURN p.age AS newAge
}
MATCH (p:Player {name: 'Player A'})
RETURN x AS iteration, newAge, p.age AS totalAge
| 迭代 | newAge | totalAge |
|---|---|---|
1 |
22 |
24 |
2 |
23 |
24 |
3 |
24 |
24 |
行数: 3 |
||
CALL 子查询的作用域效果意味着,在每行执行期间完成的工作可以在其执行结束后立即清除,然后才处理下一行。这通过确保在子查询执行期间创建的临时数据结构在其有用性之外不会持久存在,从而实现高效的资源管理并减少内存开销。因此,CALL 子查询有助于保持最佳性能和可伸缩性,尤其是在复杂或大规模查询中。
在此示例中,CALL 子查询用于 collect 一个包含为特定团队效力的所有玩家的 LIST。
MATCH (t:Team)
CALL (t) {
MATCH (p:Player)-[:PLAYS_FOR]->(t)
RETURN collect(p) as players
}
RETURN t AS team, players
| team | players |
|---|---|
|
|
|
|
|
|
行数: 3 |
|
CALL 子查询确保每个 Team 都被单独处理(每个 Team 节点一行),而不是在将它们收集到列表中之前必须同时将所有 Team 和 Player 节点保存在堆内存中。因此,使用 CALL 子查询可以减少操作所需的堆内存量。
导入变量
来自外部作用域的变量必须显式导入到 CALL 子查询的内部作用域中,这可以通过使用变量作用域子句或导入 WITH 子句(已弃用)来实现。由于子查询针对每个传入输入行进行评估,因此导入的变量将分配该行中相应的值。
变量作用域子句
变量可以使用作用域子句导入到 CALL 子查询中:CALL (<variable>)。使用作用域子句会禁用已弃用的导入 WITH 子句。
作用域子句可用于导入外部作用域的所有变量、特定变量或不导入任何变量。
此示例仅从外部作用域导入变量 p,并使用它为每个 Player 节点创建新的随机生成的 rating 属性。然后返回具有最高 rating 的 Player 节点。
MATCH (p:Player), (t:Team)
CALL (p) {
WITH rand() AS random
SET p.rating = random
RETURN p.name AS playerName, p.rating AS rating
}
RETURN playerName, rating, t AS team
ORDER BY rating
LIMIT 1
| playerName | rating | team |
|---|---|---|
|
|
|
行数: 1 |
||
要导入其他变量,请将它们包含在 CALL 后的括号内,并用逗号分隔。例如,要在上述查询中导入 MATCH 子句中的两个变量,请相应地修改作用域子句:CALL (p, t)。
要从外部作用域导入所有变量,请使用 CALL (*)。此示例导入变量 p 和 t,并为两者设置新的 lastUpdated 属性。
MATCH (p:Player), (t:Team)
CALL (*) {
SET p.lastUpdated = timestamp()
SET t.lastUpdated = timestamp()
}
RETURN p.name AS playerName,
p.lastUpdated AS playerUpdated,
t.name AS teamName,
t.lastUpdated AS teamUpdated
LIMIT 1
| playerName | playerUpdated | teamName | teamUpdated |
|---|---|---|---|
|
|
|
|
行数: 1 |
|||
要从外部作用域不导入任何变量,请使用 CALL ()。
MATCH (t:Team)
CALL () {
MATCH (p:Player)
RETURN count(p) AS totalPlayers
}
RETURN count(t) AS totalTeams, totalPlayers
| totalTeams | totalPlayers |
|---|---|
|
|
行数: 1 |
|
|
自 Neo4j 5.23 起,不带变量作用域子句使用 已弃用
|
规则
-
作用域子句的变量可以在子查询中全局引用。子查询中后续的
WITH不能取消列出已导入的变量。已弃用的导入WITH子句行为不同,因为导入的变量只能从第一行引用,并且可以被后续子句取消列出。 -
变量不能在作用域子句中被赋予别名。只允许简单的变量引用。
MATCH (t:Team)
CALL (t AS teams) {
MATCH (p:Player)-[:PLAYS_FOR]->(teams)
RETURN collect(p) as players
}
RETURN t AS teams, players
-
作用域子句的变量不能在子查询中重新声明。
MATCH (t:Team)
CALL (t) {
WITH 'New team' AS t
MATCH (p:Player)-[:PLAYS_FOR]->(t)
RETURN collect(p) as players
}
RETURN t AS team, players
-
子查询不能返回在外部作用域中已存在的变量名。要返回导入的变量,它们必须重命名。
MATCH (t:Team)
CALL () {
RETURN 1 AS t
}
RETURN t
导入 WITH 子句
变量也可以使用导入 WITH 子句导入到 CALL 子查询中。请注意,此语法不符合 GQL 规范。
WITH 子句导入的变量MATCH (t:Team)
CALL {
WITH t
MATCH (p:Player)-[:PLAYS_FOR]->(t)
RETURN collect(p) as players
}
RETURN t AS teams, players
点击阅读更多关于使用 WITH 子句导入变量的信息
-
与使用变量作用域子句一样,使用导入
WITH子句的子查询不能返回在外部作用域中已存在的变量名。要返回导入的变量,它们必须重命名。 -
导入
WITH子句必须是子查询的第一个子句(如果直接跟在USE子句之后,则是第二个子句)。 -
导入
WITH子句之后不能跟以下任何子句:DISTINCT、ORDER BY、WHERE、SKIP和LIMIT。
尝试上述任何操作都将抛出错误。例如,以下在导入 WITH 子句后使用 WHERE 子句的查询将抛出错误
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WHERE size(l) > 2
RETURN l AS largeLists
}
RETURN largeLists
Importing WITH should consist only of simple references to outside variables.
WHERE is not allowed.
解决此限制的方法(对于导入 WITH 子句的任何过滤或排序都是必需的)是在导入 WITH 子句之后声明第二个 WITH 子句。第二个 WITH 子句将作为常规 WITH 子句。例如,以下查询不会抛出错误
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WITH l
WHERE size(l) > 2
RETURN l AS largeLists
}
RETURN largeLists
| largeLists |
|---|
|
|
行数: 2 |
可选子查询调用
OPTIONAL CALL 允许可选地执行 CALL 子查询。类似于 OPTIONAL MATCH,OPTIONAL CALL 子查询产生的任何空行将返回 null。
CALL 和 OPTIONAL CALL 的区别此示例查找每个 Player 所效力的团队,突出了使用 CALL 和 OPTIONAL CALL 之间的区别。
CALLMATCH (p:Player)
CALL (p) {
MATCH (p)-[:PLAYS_FOR]->(team:Team)
RETURN team
}
RETURN p.name AS playerName, team.name AS team
| playerName | team |
|---|---|
|
|
|
|
|
|
|
|
|
|
行数: 5 |
|
请注意,未返回 Player C 的任何结果,因为它们未通过 PLAYS_FOR 关系连接到任何 Team。
OPTIONAL CALL 的查询MATCH (p:Player)
OPTIONAL CALL (p) {
MATCH (p)-[:PLAYS_FOR]->(team:Team)
RETURN team
}
RETURN p.name AS playerName, team.name AS team
现在,所有 Player 节点都将被返回,无论它们是否具有连接到 Team 的任何 PLAYS_FOR 关系。
| playerName | team |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
行数: 6 |
|
CALL 子查询的执行顺序
外部作用域中的行传递到子查询的顺序未定义。如果子查询的结果依赖于这些行的顺序,请在 CALL 子句之前使用 ORDER BY 子句以保证行的特定处理顺序。
CALL 子查询之前对结果排序此示例创建一个按 age 升序排列的所有 Player 节点的链表。
CALL 子句依赖于传入行的顺序来确保创建正确排序的链表,因此传入行必须使用前置的 ORDER BY 子句进行排序。
CALL 子查询之前对结果排序MATCH (player:Player)
WITH player
ORDER BY player.age ASC LIMIT 1
SET player:ListHead
WITH *
MATCH (nextPlayer: Player&!ListHead)
WITH nextPlayer
ORDER BY nextPlayer.age
CALL (nextPlayer) {
MATCH (current:ListHead)
REMOVE current:ListHead
SET nextPlayer:ListHead
CREATE(current)-[:IS_YOUNGER_THAN]->(nextPlayer)
RETURN current AS from, nextPlayer AS to
}
RETURN
from.name AS name,
from.age AS age,
to.name AS closestOlderName,
to.age AS closestOlderAge
| name | age | closestOlderName | closestOlderAge |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
行数: 5 |
|||
UNION 后处理
CALL 子查询可用于进一步处理 UNION 查询的结果。
CALL 子查询中使用 UNION此示例查询查找图中年龄最小和年龄最大的 Player。
CALL () {
MATCH (p:Player)
RETURN p
ORDER BY p.age ASC
LIMIT 1
UNION
MATCH (p:Player)
RETURN p
ORDER BY p.age DESC
LIMIT 1
}
RETURN p.name AS playerName, p.age AS age
| playerName | age |
|---|---|
|
|
|
|
行数: 2 |
|
如果结果的不同部分需要以不同方式匹配,并且需要对整个结果进行一些聚合,则需要使用子查询。下面的示例查询结合 UNION ALL 使用 CALL 子查询来确定图中每个 Team 的欠款或应收款金额。
MATCH (t:Team)
CALL (t) {
OPTIONAL MATCH (t)-[o:OWES]->(other:Team)
RETURN o.dollars * -1 AS moneyOwed
UNION ALL
OPTIONAL MATCH (other)-[o:OWES]->(t)
RETURN o.dollars AS moneyOwed
}
RETURN t.name AS team, sum(moneyOwed) AS amountOwed
ORDER BY amountOwed DESC
| team | amountOwed |
|---|---|
|
|
|
|
|
|
行数: 3 |
|
聚合
返回型子查询会改变查询的结果数量。CALL 子查询的结果是针对每个输入行评估子查询的组合结果。
CALL 子查询更改外部查询返回的行以下示例查找每个 Player 的名称以及他们所效力的团队。未返回 Player C 的任何行,因为他们未通过 PLAYS_FOR 关系连接到 Team。因此,子查询的结果数量改变了包围查询的结果数量。
MATCH (p:Player)
CALL (p) {
MATCH (p)-[:PLAYS_FOR]->(team:Team)
RETURN team.name AS team
}
RETURN p.name AS playerName, team
| playerName | team |
|---|---|
|
|
|
|
|
|
|
|
|
|
行数: 5 |
|
CALL 子查询和独立聚合子查询也可以执行独立聚合。以下示例使用 sum() 函数计算图中 Team 节点之间欠款的总额。请注意,Team A 的 owedAmount 是两个 OWES 关系指向 Team B 的聚合结果。
MATCH (t:Team)
CALL (t) {
MATCH (t)-[o:OWES]->(t2:Team)
RETURN sum(o.dollars) AS owedAmount, t2.name AS owedTeam
}
RETURN t.name AS owingTeam, owedAmount, owedTeam
| owingTeam | owedAmount | owedTeam |
|---|---|---|
|
|
|
|
|
|
|
|
|
行数: 4 |
||
关于返回型子查询和单元子查询的说明
上述示例都使用了以 RETURN 子句结尾的子查询。这些子查询被称为返回型子查询。
子查询针对每个传入输入行进行评估。返回型子查询的每个输出行都与输入行组合,以构建子查询的结果。这意味着返回型子查询会影响行数。如果子查询不返回任何行,则子查询之后将没有可用行。
没有 RETURN 语句的子查询称为单元子查询。单元子查询用于通过 CREATE、MERGE、SET 和 DELETE 等子句更改图的能力。它们不显式返回任何内容,这意味着子查询之后存在的行数与进入子查询之前的行数相同。
单元子查询
单元子查询用于通过更新子句更改图的能力。它们不影响包围查询返回的行数。
此示例查询为图中每个现有 Player 节点创建 3 个克隆。由于子查询是单元子查询,它不改变包围查询的行数。
MATCH (p:Player)
CALL (p) {
UNWIND range (1, 3) AS i
CREATE (:Person {name: p.name})
}
RETURN count(*)
| count(*) |
|---|
|
行数: 1 |
总结
-
CALL子查询优化数据处理和查询效率,并且可以对数据库进行更改。 -
CALL子查询允许逐行数据转换,并支持跨多行累积结果,从而促进依赖中间数据或聚合数据的复杂操作。 -
CALL子查询只能在通过变量作用域子句或导入WITH子句(已弃用)显式导入变量时引用包围查询中的变量。 -
从
CALL子查询返回的所有变量之后都可在包围查询中使用。 -
返回型子查询(带
RETURN子句)会影响输出行数,而单元子查询(不带RETURN子句)执行图更新而不改变行数。 -
ORDER BY子句可以在CALL子查询之前使用,以确保特定顺序。 -
CALL子查询可以与UNION结合使用,以处理和聚合查询结果的不同部分。