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
之间的区别。
CALL
MATCH (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
结合使用,以处理和聚合查询结果的不同部分。