条件式 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,则该行将被清除,这在子查询本身中通常没问题(如果布鲁斯还不是孤儿,您就不想创建蝙蝠侠),但您通常希望无论子查询中发生了什么都继续执行,并且可能返回一个布尔值,表示条件是否成功。
有一些变通方法可以避免行被清除。
在子查询返回之前使用独立聚合来恢复一行
当没有其他非聚合变量作为分组键时,聚合(例如 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 是针对给定列表中的每个元素执行的特性。如果列表有一个元素,则 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 查询的需求如何。
完整的签名
|
根据条件,使用给定的 |
|
根据条件,使用给定的 |
|
给定条件/只读查询对的列表,执行与第一个评估为真的条件关联的查询(如果都不为真,则执行 |
|
给定条件/写入查询对的列表,执行与第一个评估为真的条件关联的查询(如果都不为真,则执行 |
在所有情况下,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 之后继续执行查询,条件查询必须返回一些内容
目前,当执行非空条件查询且查询不返回任何内容时,不会为行生成任何 YIELD,从而清除该行。对于原始行,CALL 之后的任何内容现在都是无操作,因为不再有行可供执行(Cypher 操作按行执行)。
虽然这对于条件 CALL 是查询的最后一部分(因此之后没有更多要执行的操作)可能没问题,但对于任何想要继续查询却忘记在条件查询中添加 RETURN 的人来说,这种行为将是一个不受欢迎且令人困惑的惊喜。
由此产生的症状是,查询执行到条件 CALL,但(可能对于所有行,也可能只对于子集)CALL 之后的查询部分没有被执行。
为了避免混淆,最好在所有条件查询中始终包含 RETURN(除了您完全留空的那些,例如无操作的 else
查询……它们按预期行为)。
这种经常令人困惑的行为将在 2020 年的 APOC 后续更新中修复。
此页面有帮助吗?