条件语句下的 Cypher 执行
在某些情况下,您将编写需要一些条件逻辑的 Cypher 查询,您希望根据情况执行不同的 Cypher 语句。
目前,Cypher 不包含用于解决这种情况的原生条件功能,但有一些可以使用的工作区。
本文介绍了执行条件 Cypher 语句的方法。
在 4.1+ 中使用关联子查询
Neo4j 4.1 引入了关联子查询,让我们可以使用中间查询中存在的变量执行子查询。通过结合子查询的使用和过滤,我们可以使用子查询来实现条件 Cypher 执行。
这要求在子查询 CALL 块中使用 WITH 作为第一个子句,目的是将变量导入到子查询中。
此导入使用有一些特殊的限制,这些限制通常不适用于 WITH 的使用。
-
您只能包含来自外部查询的变量,不能包含其他任何变量。您不能在初始 WITH 中执行计算、聚合或引入新变量。
-
您不能在初始 WITH 中对任何变量进行别名。
-
您不能在初始 WITH 之后使用 WHERE 子句进行过滤。
如果您尝试执行任何这些操作,您将会遇到某种错误,例如
Importing WITH should consist only of simple references to outside variables. Aliasing or expressions are not supported.
或者更隐蔽的是,如果您尝试在初始 WITH 之后使用 WHERE 子句
Variable `x` not defined
(其中变量是 WITH 子句中出现的第一个变量)
您可以通过简单地在用于导入到子查询的初始 WITH 之后引入另一个 WITH 子句来绕过所有这些限制,如下所示
MATCH (bruce:Person {name:'Bruce Wayne'})
CALL {
WITH bruce
WITH bruce
WHERE bruce.isOrphan
MERGE (batman:Hero {name:'Batman'})
CREATE (bruce)-[:SuperheroPersona]->(batman)
WITH count(batman) as count
RETURN count = 1 as isBatman
}
RETURN isBatman
这演示了通过添加第二个 WITH 子句来过滤导入到子查询的变量的能力,这个 WITH 子句没有与用于导入到子查询的初始 WITH 相同的限制。
子查询必须返回一行,以便外部查询继续执行
子查询不独立于外部查询,如果它们不生成任何行,则外部查询将没有行来继续执行。
这可能是条件 Cypher 出现问题的原因,因为根据定义,您正在评估条件作为过滤器,以确定是否执行某些操作。
如果该条件评估为假,则该行将被清除,这通常在子查询本身中是可以的(如果您不想在布鲁斯还不是孤儿的时候创建蝙蝠侠),但您通常希望无论子查询中发生了什么,都继续执行,并且可能返回一个布尔值,表示条件是否成功。
有一些方法可以避免行被清除。
在子查询返回之前使用独立聚合来恢复一行
聚合(例如 count()),当没有其他非聚合变量作为分组键存在时,即使该行已被清除,也可以恢复该行。
这是因为获取 0 行的 count() 或对 0 行进行 collect() 以生成一个空集合是有效的。
同样,当您执行此聚合时,必须不存在其他非聚合变量。
在上面的示例中,我们在子查询中使用了这种技术,以便外部查询无论条件如何评估都可以继续执行。
WITH count(batman) as count
RETURN count = 1 as isBatman
使用该 count(),无论查询如何评估,我们将获得 0 或 1,这使得我们可以在子查询完成后继续执行。
使用 UNION 子查询来涵盖所有可能的条件
我们可以在子查询中使用 UNION,其中所有联合查询的集合涵盖所有可能的条件结果。这确保将存在一条成功的执行路径,并返回一行,使外部查询能够继续执行。
这对于将 if/else 或 case 逻辑保持在一起也很有用,否则您将不得不为每个条件块使用单独的子查询。
使用这种方法,您不再需要使用聚合来确保行保持不变,您只需要确保无论如何,至少有一个 UNIONed 查询会成功即可。
MATCH (bruce:Person {name:'Bruce Wayne'})
CALL {
WITH bruce
WITH bruce
WHERE bruce.isOrphan
MERGE (batman:Hero {name:'Batman'})
CREATE (bruce)-[:SuperheroPersona]->(batman)
RETURN true as isBatman
UNION
WITH bruce
WITH bruce
WHERE NOT coalesce(bruce.isOrphan, false)
SET bruce.name = 'Bruce NOT BATMAN Wayne'
RETURN false as isBatman
}
RETURN isBatman
请注意,我们必须为每个 UNIONed 查询使用导入 WITH,以确保它们都从外部查询中导入变量,并且我们仍然必须使用第二个 WITH 来允许我们进行过滤。
由于对可以联合在一起的查询数量没有限制,因此可以使用这种方法处理多个条件评估。
使用 FOREACH 进行只写 Cypher
FOREACH 子句可用于执行等效于 IF 条件的操作,其限制是只能使用写入子句(MERGE、CREATE、DELETE、SET、REMOVE)。
这依赖于 FOREACH 子句中 Cypher 针对给定列表中的每个元素执行的特性。如果列表包含 1 个元素,则 FOREACH 中的 Cypher 将执行。如果列表为空,则包含的 Cypher 将不会执行。
我们可以使用 CASE 评估布尔条件并输出一个单元素列表或一个空列表,这将驱动条件 Cypher 执行(执行后续的只写子句或不执行)。
例如
MATCH (node:Node {id:12345})
FOREACH (i in CASE WHEN node.needsUpdate THEN [1] ELSE [] END |
SET node.newProperty = 5678
REMOVE node.needsUpdate
SET node:Updated)
...
要获得 if/else 逻辑的等效结果,必须为 else 部分使用单独的 FOREACH。
请记住,任何其他非写入子句(例如 MATCH、WITH 和 CALL)都无法使用这种方法。
APOC 条件过程
或者,APOC 过程库包含专门用于 条件 Cypher 执行 的过程。
过程有两种类型
apoc.when()
- 当您只有 if
(可能还有 else
)查询要根据单个条件执行时。不能写入图。
apoc.case()
- 当您想要检查一系列独立条件时,每个条件都有自己的单独 Cypher 查询,如果条件为真,则执行该查询。只有第一个评估为真的条件会执行其关联的查询。如果没有条件为真,则可以提供 else 查询作为默认值。不能写入图。
读写变体
上面显示的过程仅具有读取权限,不允许写入图,因此如果条件 Cypher 中有任何写入操作,查询将出错。
有一些变体可以写入图
apoc.do.when()
- 与 apoc.when()
相似的条件 if/else Cypher 执行,但允许写入图。
apoc.do.case()
- 与 apoc.case()
相似的条件 case Cypher 执行,但允许写入图。
这是必要的,因为过程的读写模式必须在过程代码中声明。
仅具有只读过程将无法写入图。
仅具有写入功能的过程意味着它无法被只读用户调用,即使条件 Cypher 未执行任何写入操作。
这两种方法都是必要的,以便无论用户类型或条件 Cypher 查询的需求如何,都能提供完整的功能。
完整的签名
|
根据条件,使用给定的 params 执行只读 ifQuery 或 elseQuery。 |
|
根据条件,使用给定的 params 执行写入 ifQuery 或 elseQuery。 |
|
给定一个条件/只读查询对列表,执行第一个条件为真(或如果都不为真则执行 |
|
给定一个条件/写入查询对列表,执行第一个条件为真(或如果都不为真则执行 |
在所有情况下,condition
必须是一个布尔表达式,所有条件查询(ifQuery
、elseQuery
、query
)实际上都是Cypher查询字符串,并且必须用引号括起来。
因此,请注意在查询字符串中正确处理引号。如果查询字符串本身在双引号中,则该查询中的所有字符串都应使用单引号(反之亦然)。
使用这些过程可能很棘手。以下是一些更实用的技巧,可以帮助您避免最常见的陷阱。
处理复杂嵌套查询中的引号/转义符
对于更复杂的查询(例如必须在多个级别处理引号的嵌套查询),请考虑先将查询字符串定义为一个变量,然后将该变量传递给过程,或者将条件查询作为参数传递给查询本身。这可能会使您免受处理Java字符串中转义字符的困扰。
传递必须在条件查询中可见的参数
执行时,条件Cypher查询无法看到CALL外部的变量。
如果查询必须查看或使用变量,请将其作为params
映射参数的一部分传递给调用,如下所示
MATCH (bruceWayne:Person {name:'Bruce Wayne'})
CALL apoc.do.when(bruceWayne.isOrphan, "MERGE (batman:Hero {name:'Batman'}) CREATE (bruce)-[:SuperheroPersona]->(batman) RETURN bruce", "SET bruce.name = 'Bruce NOT BATMAN Wayne' RETURN bruce", {bruce:bruceWayne}) YIELD value
...
参数映射是调用的最后一个参数:{bruce:bruceWayne}
,它允许所有条件查询将bruceWayne
变量作为bruce
访问。如果需要,可以将其他参数添加到参数映射中。
如果要在CALL后继续执行查询,则条件查询必须返回一些内容
目前,当执行(非空)条件查询并且查询没有返回任何内容时,该行不会产生任何YIELD结果,从而擦除该行。对于原始行,CALL后的任何操作现在都将变成无操作,因为不再有要执行的行(Cypher操作按行执行)。
虽然这对于条件CALL是查询的最后一部分(因此CALL后没有更多要执行的操作)时可能没问题,但对于想要继续查询但忘记在条件查询中添加RETURN的任何人来说,这种行为将是令人不快和困惑的意外。
由此产生的症状是查询会执行到条件CALL,但(可能对于所有行,也可能仅对于子集)CALL后的查询部分不会执行。
为了避免混淆,可能有助于始终在所有条件查询中包含RETURN(除了您完全留空的查询,例如无操作的else
查询……它们的执行结果与预期一致)。
这种经常令人困惑的行为将在2020年之后的APOC更新中修复。
此页面对您有帮助吗?