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
| iteration | 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 |
|
|
不带变量作用域子句的 已弃用
|
规则
-
作用域子句的变量可以在子查询中全局引用。子查询内的后续
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
42I28: 错误:语法错误或访问规则违规 - 导入的 WITH 使用无效。导入的 WITH 只能包含对外部变量的直接引用。不允许使用 'WHERE'。 42001:错误:语法错误或访问规则冲突 - 无效语法 |
解决此限制(对于任何导入的 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 子查询仅限 Cypher 25Neo4j 2025.06 引入
可以在 CALL 子查询中使用 WHEN,以便在谓词评估为 true 时有条件地执行分支。请注意,不同 WHEN 分支返回的列的名称和数量必须相同。有关详细信息,请参阅 条件查询 → 条件子查询。
CALL 子查询此示例使用条件逻辑,通过执行 WHEN/ELSE 分支根据年龄对球员进行分类,并根据年龄为每个球员设置 "Senior" 或 "Junior" 的 ageGroup 属性。
MATCH (t:Team)
OPTIONAL MATCH (p:Player)-[:PLAYS_FOR]->(t)
CALL (*) {
WHEN p.age > 25 THEN {
SET p.ageGroup = "Senior"
RETURN p.name AS player, p.ageGroup AS ageGroup
}
ELSE {
SET p.ageGroup = "Junior"
RETURN p.name AS player, p.ageGroup AS ageGroup
}
}
RETURN player, ageGroup
| player | ageGroup |
|---|---|
|
|
|
|
|
|
|
|
|
|
行:5 |
|
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 |
|||
联合后处理
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 |
|
如果结果的不同部分需要以不同方式匹配,并且需要对整个结果进行聚合,则需要使用子查询。下面的示例查询将 CALL 子查询与 UNION ALL 结合使用,以确定图中每个 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 是两条指向 Team B 的 OWES 关系的聚合结果。
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子句)在执行图更新时不会更改行数。 -
可以在
CALL子查询之前使用ORDER BY子句以确保特定顺序。 -
CALL子查询可以与UNION结合使用,以处理和聚合查询结果的不同部分。