知识库

UNION 后处理

Cypher 不允许对 `UNION` 或 `UNION ALL` 结果进行进一步处理,因为所有联合查询都需要 `RETURN`。

以下是一些解决方法。

Neo4j 4.0 中的 UNION 后处理

在 Neo4j 4.0 中,现在可以通过 子查询 进行 UNION 后处理。

使用示例

CALL {
  MATCH (movieOrPerson:Movie) RETURN movieOrPerson
  UNION
  MATCH (movieOrPerson:Person) RETURN movieOrPerson
}
WITH movieOrPerson
...

这使我们能够继续处理 UNION 子查询的结果。

但是,在最初的 4.0 版本中,只支持不相关子查询,这意味着子查询不能使用调用外部的变量。这意味着在更复杂的查询中间使用子查询进行 UNION 后处理可能不可行,因为无法将外部查询中的变量传递给子查询使用。

在 4.0.x 中,相关子查询(更实用,可以使用子查询外部的变量)目前只有在使用 Neo4j Fabric 时才可用。

4.1+ 中的 UNION 后处理增强功能

随着 4.1 的发布,CALL 子查询功能得到了增强,允许相关子查询。这使我们能够在子查询中使用现有的变量。

这需要在子查询 CALL 块中使用 `WITH` 作为第一个子句,用于将变量导入子查询。

在使用 `UNION` 或 `UNION ALL` 时,我们可以为每个联合查询提供类似的导入 `WITH` 子句

MATCH (m:Movie {title:'The Matrix'})
CALL {
    WITH m
    MATCH (m)<-[:ACTED_IN]-(p)
    RETURN p
    UNION
    WITH m
    MATCH (m)<-[:DIRECTED]-(p)
    RETURN p
}
RETURN p.name as name
ORDER BY name ASC

这将正确返回 The Matrix 中所有演员和导演的按字母顺序排序的姓名。

此导入用法有一些不适用于 `WITH` 用法的特殊限制

  1. 你只能包括来自外部查询的变量,不能包括其他任何变量。+ 你不能在初始 `WITH` 中执行计算、聚合或引入新变量。

  2. 你不能在初始 `WITH` 中为任何变量设置别名。

  3. 你不能在初始 `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 (m:Movie)
WHERE m.title CONTAINS 'Matrix'
CALL {
    WITH m
    WITH m as movie
    MATCH (m)<-[:DIRECTED]-(p)
    RETURN p.name as name
    UNION
    WITH m
    WITH m
    WHERE m.title CONTAINS 'Reloaded'
    MATCH (m)<-[:ACTED_IN]-(p)
    RETURN p.name as name
}
RETURN DISTINCT name
ORDER BY name ASC

这演示了在使用第二个 `WITH` 子句(不受限于用于将变量导入子查询的初始 `WITH` 的相同限制)时,能够为导入的变量设置别名以及能够过滤导入的变量。

对于 3.5.x 及更早版本

对于早期版本,本机子查询不可用,因此必须使用其他解决方法。

组合集合,然后使用 `UNWIND` 返回到行并应用 `DISTINCT`

MATCH (m:Movie)
WITH collect(m) AS movies
MATCH (p:Person)
WITH movies + collect(p) AS moviesAndPeople
UNWIND moviesAndPeople AS movieOrPerson
WITH DISTINCT movieOrPerson
...

在上面的查询中,`DISTINCT` 实际上并不需要,但如果结果可能存在于多个要组合的集合中(假设你想要唯一值),则需要使用 `DISTINCT`。

使用 apoc.cypher.run() 从子查询返回 UNION 结果

使用 APOC 过程,你可以使用 `apoc.cypher.run()` 在子查询中执行 `UNION`,并返回其结果。

CALL apoc.cypher.run('
 MATCH (movieOrPerson:Movie)
 RETURN movieOrPerson
 UNION
 MATCH (movieOrPerson:Person)
 RETURN movieOrPerson',
 {}) yield value
WITH value.movieOrPerson as movieOrPerson
...

请记住,过程调用是按行执行的,因此当已经存在多行时使用这种方法可能会导致意外且不可预知的结果。