调用子查询
示例图
以下示例使用具有以下架构的图
要重新创建该图,请在空的 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
子查询中: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 (t) {
RETURN 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
子查询之前排序结果此示例创建了所有Player
节点的链接列表,按age
升序排列。
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
之类的子句更改图的能力。它们没有显式返回任何内容,这意味着子查询之后存在的行数与进入子查询的行数相同。