知识库

限制每行 MATCH 结果数

由于 LIMIT 适用于查询的总行数,因此在从多个节点匹配时,如果限制必须针对每行匹配结果,则无法使用它。

以电影数据库为例。

如果你需要一个查询来获取《黑客帝国》中的所有演员,并针对每个演员,获取该演员参演的 3 部电影,那么第一次(不正确)尝试可能如下所示:

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
                   MATCH (p)-[:ACTED_IN]->(m)
                   RETURN p, m LIMIT 3

上述查询不会返回期望的结果。LIMIT 反而会使查询总共只返回 3 行。

以下是一些针对每行匹配结果应用限制的解决方案:

在 4.1+ 版本中在子查询内使用 LIMIT

Neo4j 4.1 引入了关联子查询,允许我们使用查询中存在的变量执行子查询。

由于子查询是按行执行的,我们可以在子查询中执行 MATCH 并应用 LIMIT,这为我们提供了限制每行匹配结果的最简单方法。

这要求在子查询 CALL 块中将 WITH 作为第一个子句使用,目的是将变量导入到子查询中。

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
CALL {
    WITH p
    MATCH (p)-[:ACTED_IN]->(m)
    RETURN m
    LIMIT 3
}

RETURN p, m

这将正确返回《黑客帝国》中的所有演员,以及他们参演的最多 3 部电影。

如果我们希望每个演员只出现一次,并包含每位演员最多 3 部电影的集合,我们可以在限制后,在子查询中收集这些电影

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
CALL {
    WITH p
    MATCH (p)-[:ACTED_IN]->(m)
    WITH m
    LIMIT 3
    RETURN collect(m) as movies
}

RETURN p, movies

请注意,此 WITH 导入有一些特殊限制,这些限制通常不适用于 WITH 的常规用法

  1. 你只能包含来自外部查询的变量,不能包含其他变量。

  2. 你不能在初始 WITH 中执行计算、聚合或引入新变量。

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

  4. 你不能在初始 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 (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
CALL {
    WITH p
    WITH p as actor
    MATCH (actor)-[:ACTED_IN]->(m)
    RETURN m
    LIMIT 3
}

RETURN p, m

这表明我们如何可以为导入的变量设置别名(或者,如果需要,进行过滤),但不能在初始导入的 WITH 本身中进行。

适用于 4.0.x 及更早版本

对于早期版本,原生关联子查询不可用,因此必须使用其他变通方法。

获取集合中感兴趣的部分

一种常见的解决方案是 collect() 并获取感兴趣的部分

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
MATCH (p)-[:ACTED_IN]->(m)
RETURN p, collect(m)[..3] AS movies

在 Neo4j 3.1.x 及更高版本中,你可以使用模式推导作为简写方法

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
RETURN p, [(p)-[:ACTED_IN]->(m) | m][..3] as movies

如果只需要集合中的一个元素,可以使用 head() 函数从模式推导中获取第一个元素

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
RETURN p, head([(p)-[:ACTED_IN]->(m) | m]) as movie

虽然这在每个节点关系较少时有效,但在关系数量庞大的超级节点上可能会变得不可行,因为它必须在收集之前展开所有 :ACTED_IN 关系。

使用 apoc.cypher.run() 执行有限子查询

Neo4j 目前除了模式推导之外,不提供原生子查询支持,但即使是模式推导也不支持 LIMIT

然而,在 Neo4j 3.0.x 及更高版本中,使用 APOC 过程,你可以使用 apoc.cypher.run() 执行带有 LIMIT 的子查询,由于它是按行执行的,因此其行为符合我们的期望。

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
CALL apoc.cypher.run('
 WITH {p} AS p
 MATCH (p)-[:ACTED_IN]->(m)
 RETURN m LIMIT 3',
 {p:p}) YIELD value
RETURN p, value.m AS movie

这种方法是高效的,因为通过使用 LIMIT,我们无需为扩展所有 :ACTED_IN 关系付出代价,我们只需每行扩展 3 个。

使用 APOC 路径扩展器,利用结束节点或终止过滤器和 limit 参数

在 Neo4j 3.1.3 及更高版本,以及 APOC 过程 3.1.3.6 及更高版本中,你可以使用新的路径扩展器功能来限制对某些节点的扩展。

limit 参数仅适用于接受配置映射的路径扩展器过程,并且仅在使用结束节点(>)或终止标签过滤器(/)时才可用

  • apoc.path.expandConfig()

  • apoc.path.subgraphNodes()

  • apoc.path.subgraphAll()

  • apoc.path.spanningTree()

使用这种方法,查询变为

MATCH (:Movie{title:'The Matrix'})<-[:ACTED_IN]-(p:Person)
CALL apoc.path.subgraphNodes(p, {relationshipFilter:'ACTED_IN>', labelFilter:'/Movie', limit:3}) YIELD node
RETURN p, node as movie
© . All rights reserved.