顺序查询 (NEXT)

NEXT 允许将查询线性组合成一系列更小、独立的片段,并将整个中间结果表从一个片段传递到下一个片段。

NEXT 具有以下优势:

  • NEXT 可以提高复杂查询的模块化程度和可读性。

  • NEXT 可用于替代 WITH 子句来构建复杂查询。

  • NEXT 可以提高 条件查询 (WHEN) 的可用性。

  • NEXT 允许将完整的中间结果表传递到 UNION 的各个分支中。

示例图

本页示例中使用以下图数据库结构

要重建该图,请对空的 Neo4j 数据库运行以下查询。

CREATE (techCorp:Supplier {name: 'TechCorp', email: 'contact@techcorp.com'}),
       (foodies:Supplier {name: 'Foodies Inc.', email: 'info@foodies.com'}),

       (laptop:Product {name: 'Laptop', price: 1000}),
       (phone:Product {name: 'Phone', price: 500}),
       (headphones:Product {name: 'Headphones', price: 250}),
       (chocolate:Product {name: 'Chocolate', price: 5}),
       (coffee:Product {name: 'Coffee', price: 10}),

       (amir:Customer {firstName: 'Amir', lastName: 'Rahman', email: 'amir.rahman@example.com', discount: 0.1}),
       (keisha:Customer {firstName: 'Keisha', lastName: 'Nguyen', email: 'keisha.nguyen@example.com', discount: 0.2}),
       (mateo:Customer {firstName: 'Mateo', lastName: 'Ortega', email: 'mateo.ortega@example.com', discount: 0.05}),
       (hannah:Customer {firstName: 'Hannah', lastName: 'Connor', email: 'hannah.connor@example.com', discount: 0.15}),
       (leila:Customer {firstName: 'Leila', lastName: 'Haddad', email: 'leila.haddad@example.com', discount: 0.1}),
       (niko:Customer {firstName: 'Niko', lastName: 'Petrov', email: 'niko.petrov@example.com', discount: 0.25}),
       (yusuf:Customer {firstName: 'Yusuf', lastName: 'Abdi', email: 'yusuf.abdi@example.com', discount: 0.1}),

       (amir)-[:BUYS {date: date('2024-10-09')}]->(laptop),
       (amir)-[:BUYS {date: date('2025-01-10')}]->(chocolate),
       (keisha)-[:BUYS {date: date('2023-07-09')}]->(headphones),
       (mateo)-[:BUYS {date: date('2025-03-05')}]->(chocolate),
       (mateo)-[:BUYS {date: date('2025-03-05')}]->(coffee),
       (mateo)-[:BUYS {date: date('2024-04-11')}]->(laptop),
       (hannah)-[:BUYS {date: date('2023-12-11')}]->(coffee),
       (hannah)-[:BUYS {date: date('2024-06-02')}]->(headphones),
       (leila)-[:BUYS {date: date('2023-05-17')}]->(laptop),
       (niko)-[:BUYS {date: date('2025-02-27')}]->(phone),
       (niko)-[:BUYS {date: date('2024-08-23')}]->(headphones),
       (niko)-[:BUYS {date: date('2024-12-24')}]->(coffee),
       (yusuf)-[:BUYS {date: date('2024-12-24')}]->(chocolate),
       (yusuf)-[:BUYS {date: date('2025-01-02')}]->(laptop),

       (techCorp)-[:SUPPLIES]->(laptop),
       (techCorp)-[:SUPPLIES]->(phone),
       (techCorp)-[:SUPPLIES]->(headphones),
       (foodies)-[:SUPPLIES]->(chocolate),
       (foodies)-[:SUPPLIES]->(coffee)

语法

NEXT 语法
<Query1>

NEXT

<Query2>

NEXT

<Query3>

将值传递给后续查询

NEXT 将一个查询的结果表传递给后续查询。在以下示例中,NEXT 将变量 customer 传递给第二个查询

通过 NEXT 将变量传递给另一个查询
MATCH (c:Customer)
RETURN c AS customer

NEXT

MATCH (customer)-[:BUYS]->(:Product {name: 'Chocolate'})
RETURN customer.firstName AS chocolateCustomer
结果
chocolateCustomer

"Amir"

"Mateo"

"Yusuf"

行:3

通过 NEXT 将多个变量传递给另一个查询
MATCH (c:Customer)-[:BUYS]->(p:Product {name: 'Chocolate'})
RETURN c AS customer, p AS product

NEXT

RETURN customer.firstName AS chocolateCustomer,
       product.price * (1 - customer.discount) AS chocolatePrice
结果
chocolateCustomer chocolatePrice

"Amir"

4.5

"Mateo"

4.75

"Yusuf"

4.5

行:3

当后接 NEXT 时,RETURN 子句只能包含变量或别名表达式。不允许使用字面量或未起别名的表达式。例如,RETURN 1RETURN 1 + 1 不能出现在 NEXT 之前,但 RETURN 1 AS oneRETURN 1 + 1 AS two 可以。

NEXT 之前的查询中属于局部变量且未显式作为该查询结果返回的变量,在 NEXT 的上下文中后续查询无法访问。这允许您像使用 WITH 一样控制变量作用域,请参阅 控制作用域中的变量

NEXT 之后的聚合

NEXT 将整个结果表传递给后续查询。这在聚合值时特别有用。

在以下示例中,NEXT 将变量 customer 传递给第二个查询

NEXT 之后的聚合
MATCH (c:Customer)-[:BUYS]->(p:Product)
RETURN c AS customer, p AS product

NEXT

RETURN product.name AS product,
       COUNT(customer) AS numberOfCustomers
结果
product numberOfCustomers

"Laptop"

4

"Chocolate"

3

"Headphones"

3

"Coffee"

3

"Phone"

1

行:5

UNION 查询的交互

NEXT 之后使用 UNION

如果 UNION 查询位于 NEXT 之后,则完整的中间结果表会传递到 UNION 查询的所有分支中。

NEXT 之后的 UNION
MATCH (c:Customer)-[:BUYS]->(p:Product)
RETURN c, p

NEXT

RETURN c.firstName AS name, COLLECT(p.price * (1 - c.discount)) AS purchases, "discounted price" AS type
UNION
RETURN c.firstName AS name, COLLECT(p.price) AS purchases, "real price" AS type

NEXT

RETURN * ORDER BY name, type
结果
名称 (name) purchases type

"Amir"

[900.0, 4.5]

"discounted price"

"Amir"

[1000, 5]

"real price"

"Hannah"

[212.5, 8.5]

"discounted price"

"Hannah"

[250, 10]

"real price"

"Keisha"

[200.0]

"discounted price"

"Keisha"

[250]

"real price"

"Leila"

[900.0]

"discounted price"

"Leila"

[1000]

"real price"

"Mateo"

[950.0, 4.75, 9.5]

"discounted price"

"Mateo"

[1000, 5, 10]

"real price"

"Niko"

[375.0, 187.5, 7.5]

"discounted price"

"Niko"

[500, 250, 10]

"real price"

"Yusuf"

[900.0, 4.5]

"discounted price"

"Yusuf"

[1000, 5]

"real price"

行数: 14

NEXT 之前使用 UNION

如果 UNION 查询位于 NEXT 之前,则 UNION 的完整结果会传递给后续查询。

NEXT 之前的 UNION
MATCH (c:Customer)-[:BUYS]->(:Product{name: "Laptop"})
RETURN c.firstName AS customer
UNION ALL
MATCH (c:Customer)-[:BUYS]-> (:Product{name: "Coffee"})
RETURN c.firstName AS customer

NEXT

RETURN customer AS customer, count(customer) as numberOfProducts
结果
customer numberOfProducts

"Amir"

1

"Mateo"

2

"Leila"

1

"Yusuf"

1

"Hannah"

1

"Niko"

1

行数: 6

在此示例中,来自第一个片段的客户名称列表中包含“Mateo”的重复条目,因为他同时购买了笔记本电脑和咖啡。使用 UNION ALL 将他两次添加到列表中。第二个片段可以访问该列表,因为 UNION 的两部分都返回了别名为 customer 的列表的一部分。通过使用 count(),该列表在查询的 RETURN 部分对重复项进行了聚合。

使用 {}UNION 中使用 NEXT

如果 UNION 查询的任何块中包含 NEXT,则必须用 {} 包裹该块。

UNION 内部的 NEXT
{
MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Chocolate'})
RETURN c AS customer

NEXT

RETURN customer.firstName AS plantCustomer
}

UNION ALL

{
MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Coffee'})
RETURN c AS customer

NEXT

RETURN customer.firstName AS plantCustomer
}
结果
plantCustomer

"Amir"

"Mateo"

"Yusuf"

"Mateo"

"Hannah"

"Niko"

行数: 6

CALL 子查询的交互

CALL 子查询将中间结果表逐行传递给子查询,而 NEXT 则整体传递表。如果 NEXT 被包装在 CALL 子查询中,则前一个查询的结果将一次一行地传递给 NEXT。这可用于计算分组中更复杂的聚合。

CALL 内部的 NEXT
MATCH (p:Product) WHERE p.name <> "Coffee"
CALL (p) {
    MATCH (p)<-[:BUYS]-(c:Customer)-[:BUYS]->(otherProduct)
    RETURN c, otherProduct

    NEXT

    RETURN count(DISTINCT c) AS customers, 0 AS customersAlsoBuyingCoffee
    UNION
    FILTER otherProduct.name = "Coffee"
    RETURN 0 as customers, count(DISTINCT c) AS customersAlsoBuyingCoffee

    NEXT

    RETURN max(customers) AS customers, max(customersAlsoBuyingCoffee) AS customersAlsoBuyingCoffee
}
RETURN p.name AS product,
       round(toFloat(customersAlsoBuyingCoffee) * 100 / customers, 1) AS percentageOfCustomersAlsoBuyingCoffee
  ORDER BY product
结果
product percentageOfCustomersAlsoBuyingCoffee

"Chocolate"

33.3

"Headphones"

100.0

"Laptop"

33.3

"Phone"

100.0

行:5

此示例计算对于每个非咖啡产品,同时购买了咖啡的客户百分比。对于每个产品 p,子查询会查找产品 p 的客户 c 与该客户也购买过的另一个产品 otherProduct 的所有组合。第一个 NEXT 将这些组合作为一个整体传递给 UNION,以便查询可以

  1. 计算 union 第一个分支中的所有客户。

  2. 计算 union 第二个分支中也购买了咖啡的客户。

UNION 产生两行——每个分支一行。第二个 NEXT 将这两行作为一个整体传递给一个查询,该查询将它们聚合成一行,这就是 CALL 子查询的结果。

NEXT 不能用于使用(已弃用的)导入 WITH 语法的 CALL 子查询中。

与条件查询的交互

NEXT 之前或之后使用条件查询

条件查询的处理方式与 CALL 类似,即逐行处理传入的中间结果表。位于 NEXT 之后的条件查询等同于包装在 CALL 子查询中的条件查询。

NEXT 内部的条件查询
MATCH (c:Customer)-[:BUYS]->(:Product)<-[:SUPPLIES]-(s:Supplier)
RETURN c.firstName AS customer, s.name AS supplier

NEXT

WHEN supplier = "TechCorp" THEN
  RETURN customer, "Tech enjoyer" AS personality
WHEN supplier = "Foodies Inc." THEN
  RETURN customer, "Tropical plant enjoyer" AS personality

NEXT

RETURN customer, collect(DISTINCT personality) AS personalities

NEXT

WHEN size(personalities) > 1 THEN
  RETURN customer, "Enjoyer of tech and plants" AS personality
ELSE
  RETURN customer, personalities[0] AS personality
结果
customer personality

"Amir"

"Enjoyer of tech and plants"

"Mateo"

"Enjoyer of tech and plants"

"Yusuf"

"Enjoyer of tech and plants"

"Niko"

"Enjoyer of tech and plants"

"Hannah"

"Enjoyer of tech and plants"

"Leila"

"Tech enjoyer"

"Keisha"

"Tech enjoyer"

行:7

在上面的查询中,根据客户购买的产品为其分配个性类型。第二个片段是一个条件查询,它根据客户购买的供应商返回不同的基础个性类型。第三个片段聚合个性类型。最后,第四个片段是另一个条件查询,如果存在多个基础个性类型,它会将它们合并为一种新的个性。

使用 {} 在条件查询中使用 NEXT

如果条件查询的任何 THENELSE 块中有 NEXT,则必须用 {} 包裹 THENELSE 之后的部分。

条件查询内部的 NEXT
MATCH (c:Customer)-[:BUYS]->(p:Product)
RETURN c AS customer, sum(p.price) AS sum

NEXT

WHEN sum >= 1000 THEN {
  RETURN customer.firstName AS customer, "club 1000 plus" AS customerType, sum AS sum
}
ELSE {
  RETURN sum * (1 - customer.discount) AS finalSum

  NEXT

  RETURN customer.firstName AS customer, "club below 1000" AS customerType, finalSum AS sum
}
结果
customer customerType sum

"Amir"

"club 1000 plus"

1005

"Mateo"

"club 1000 plus"

1015

"Leila"

"club 1000 plus"

1000

"Yusuf"

"club 1000 plus"

1005

"Keisha"

"club below 1000"

200.0

"Hannah"

"club below 1000"

221.0

"Niko"

"club below 1000"

570.0

行:3

上面的查询计算每个客户购买的产品总价,然后仅对低于 1000 的总额应用客户折扣。

已知问题与修复

NEXT 最初无法在 UNION 分支和 CALL 子查询的上下文中正确处理聚合。自 Neo4j 2025.08 起,此问题已得到修复。下表总结了各版本中的行为变化。

Neo4j 版本 行为

2025.06

当在 UNION 分支和 CALL 子查询的上下文中使用聚合时,NEXT 会产生错误的结果。

2025.07

UNION 分支和 CALL 子查询的上下文中使用带有 NEXT 的聚合会抛出错误。

2025.08+

NEXT 正确支持在 UNION 分支和 CALL 子查询上下文中的聚合。有关详细信息,请参阅上文。