知识库

限制每行的 MATCH 结果

由于 LIMIT 作用于查询的总行数,当从多个节点匹配且限制必须针对每行的匹配结果时,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,这提供了对每行限制匹配结果的最简方式。

这需要在子查询 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 及更高版本中,你可以使用模式推导(pattern comprehension)作为简写方式

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 Procedures,可以使用 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 Procedures 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
© . This site is unofficial and not affiliated with Neo4j, Inc.