知识库

限制每行 MATCH 结果

由于LIMIT适用于查询的总行数,因此它不能用于从多个节点匹配的情况,在这种情况下,限制必须在每行匹配的结果上。

以使用 Movies 数据库为例。

如果您需要一个查询来获取所有来自 The Matrix 的演员,并为每个演员获取该演员出演的 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,这为我们提供了限制每行匹配结果的最简单方法。

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

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