条件 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 那样受到限制。
子查询必须返回一行,外部查询才能继续
子查询不独立于外部查询,如果它们不产生任何行,外部查询将没有任何行可以继续执行。
这在条件 Cypher 中可能是一个问题,因为根据定义,您是将条件作为过滤器来评估以决定是否执行某些操作。
如果该条件评估为 false,则该行会被清除,这在子查询本身内部通常是可以的(如果 Bruce 还是个孤儿,您可能不想创建 Batman),但通常您希望无论子查询中发生了什么都继续执行,并可能返回一些布尔值来表示条件是否成功。
有一些变通方法可以避免行被清除。
在子查询返回之前使用独立聚合来恢复行
当没有其他非聚合变量作为分组键时,聚合(例如 count())即使在行已被清除的情况下也可以恢复一行。
这是因为获取 0 行的 count(),或在 0 行上执行 collect() 以生成空集合是有效的。
同样,当您执行此聚合时,不得有其他非聚合变量。
在上面的示例中,我们在子查询中使用了这种技术,以便外部查询无论条件如何评估都能继续
WITH count(batman) as count
RETURN count = 1 as isBatman
有了那个 count(),无论查询如何评估,我们都会得到 0 或 1,从而让我们在子查询完成时有一行可以继续执行。
使用 UNION 子查询来覆盖所有可能的条件
我们可以改用子查询中的 UNION,其中所有联合查询的集合覆盖了所有可能的条件结果。这确保了将有一个成功的执行路径并返回一行,从而允许外部查询继续。
这也对于保持 if/else 或 case 逻辑的等价物在一起很有用,否则您将不得不为每个条件块使用单独的子查询。
使用这种方法,您不再需要使用聚合来确保行保留,您只需要确保至少有一个 UNION 查询无论如何都会成功。
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
请注意,我们必须为每个 UNION 查询使用导入 WITH,以确保它们每个都从外部查询导入变量,并且我们仍然必须使用第二个 WITH 来允许我们进行过滤。
由于可以联合的查询数量没有限制,您可以使用此方法处理多个条件评估。
为只写 Cypher 使用 FOREACH
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() - 条件 if/else Cypher 执行,类似于 apoc.when(),但允许写入图形。
apoc.do.case() - 条件 case Cypher 执行,类似于 apoc.case(),但允许写入图形。
这是必要的,因为过程的读取或写入模式必须在过程代码中声明。
仅拥有只读过程将无法写入图形。
仅拥有写入能力的过程意味着即使条件 Cypher 不执行任何写入,只读用户也无法调用它。
这两者都是必要的,以便无论用户类型或条件 Cypher 查询的需求如何,都能提供全部功能。
完整的签名
|
基于条件,使用给定的 |
|
基于条件,使用给定的 |
|
给定条件/只读查询对列表,执行与第一个评估为真的条件关联的查询(如果没有条件为真,则执行 |
|
给定条件/写入查询对列表,执行与第一个评估为真的条件关联的查询(如果没有条件为真,则执行 |
在所有情况下,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
...
params 映射是调用的最后一个参数:{bruce:bruceWayne},它允许 bruceWayne 变量以 bruce 的身份对任何条件查询可见。如果需要,可以将其他参数添加到 params 映射中。
如果您想在 CALL 之后继续执行查询,条件查询必须 RETURN 某些内容
目前,当一个(非空)条件查询被执行且查询没有 RETURN 任何内容时,该行不会 YIELD 任何东西,从而清除了该行。对于该原始行,CALL 之后的一切现在都是空操作,因为没有行可以执行(Cypher 操作按行执行)。
虽然当条件 CALL 是查询的最后一部分时(因此 CALL 之后没有更多内容可执行),这可能没问题,但对于任何想继续查询却忘记在条件查询中添加 RETURN 的人来说,这种行为将是一个不受欢迎且令人困惑的意外。
结果症状是查询执行到条件 CALL,但 CALL 之后查询的任何部分(可能对所有行,可能仅对子集)都没有被执行。
为了避免混淆,最好在所有条件查询中始终包含 RETURN(除了那些您完全留空的查询,例如空操作 else 查询……它们的行为符合预期)。
这种经常令人困惑的行为将在 2020 年的后续 APOC 更新中修复。
此页面有帮助吗?