Sequential queries (NEXT)Cypher 25 onlyIntroduced in Neo4j 2025.06
NEXT allows for linear composition of queries into a sequence of smaller, self-contained segments, passing the whole table of intermediate results from one segment to the next.
NEXT has the following benefits:
-
NEXTcan improve the modularity and readability of complex queries. -
NEXTcan be used instead of WITH clause to construct complex queries. -
NEXTcan improve the usability of Conditional queries (WHEN). -
NEXTallows for passing the full table of intermediate results into the branches of aUNION.
Example graph
The following graph is used for the examples on this page:
To recreate the graph, run the following query against an empty Neo4j database.
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)
Passing values to subsequent queries
NEXT passes the result table of a query to the subsequent query.
In the following example, NEXT passes the variable customer to the second query:
NEXTMATCH (c:Customer)
RETURN c AS customer
NEXT
MATCH (customer)-[:BUYS]->(:Product {name: 'Chocolate'})
RETURN customer.firstName AS chocolateCustomer
| chocolateCustomer |
|---|
|
|
|
Rows: 3 |
NEXTMATCH (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 |
|---|---|
|
|
|
|
|
|
Rows: 3 |
|
|
When followed by |
Variables which are local to the query preceding NEXT and which are not explicitly returned as part of the result of that query are not accessible by subsequent queries in the context of NEXT.
This allows you to control variable scope similarly to what you can do with WITH, see Control variables in scope.
Aggregation after NEXT
NEXT passes the result table as a whole to the subsequent query.
This is particularly useful when aggregating values.
In the following example, NEXT passes the variable customer to the second query:
NEXTMATCH (c:Customer)-[:BUYS]->(p:Product)
RETURN c AS customer, p AS product
NEXT
RETURN product.name AS product,
COUNT(customer) AS numberOfCustomers
| product | numberOfCustomers |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
Interactions with UNION queries
Using UNION after NEXT
If a UNION query follows a NEXT the full table of intermediate results is passed into all branches of the UNION query.
UNION after NEXTMATCH (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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 14 |
||
Using UNION before NEXT
If a UNION query precedes a NEXT the full result of the UNION is passed to the subsequent query.
UNION before NEXTMATCH (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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 6 |
|
In this example, the list of customer names from the first segment has a duplicate entry for "Mateo" who bought both a laptop and coffee.
The use of UNION ALL added him to the list twice.
The second segment can access the list, because both parts of the UNION return a part of the list, aliased as customer.
By using count(), the list aggregates the duplicate in the RETURN part of the query.
NEXT inside a UNION using {}
If a UNION query has a NEXT in any of its blocks, it is necessary to wrap that block with {}.
NEXT inside UNION{
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 |
|---|
|
|
|
|
|
|
Rows: 6 |
Interactions with CALL subqueries
CALL subqueries pass the table of intermediate results to the subquery row by row, while NEXT passes the table as a whole.
If NEXT is wrapped in a CALL subquery, the result of the preceding query is passed to NEXT a single row at a time.
This can be used to compute more complex aggregates in groups.
NEXT inside CALLMATCH (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 |
|---|---|
|
|
|
|
|
|
|
|
Rows: 5 |
|
This example computes the percentage of customers that also bought coffee for each non-coffee product.
For each product p the subquery finds all pairs of a customer c of product p and another product otherProduct that the customer has also bought.
The first NEXT passes these pairs as a whole into a UNION, so that the query can:
-
count all customers in the first branch of the union.
-
count the customers who also bought coffee in the second branch of the union.
The UNION produces two rows — one from each branch.
The second NEXT passes these two rows as a whole to a query that aggregates them into a single row, which is the result of the CALL subquery.
|
|
Interactions with conditional queries
Using conditional queries before or after NEXT
Conditional queries act similar to CALL by processing the incoming table of intermediate result row by row.
A conditional query following a NEXT acts equivalent to a conditional query wrapped in a CALL subquery.
NEXTMATCH (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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 7 |
|
In the query above, customers are assigned personality types based on the products they purchased. The second segment is a conditional query that returns different base personality types for different suppliers the customers purchased from. The third segment aggregates the personality types. Finally, the fourth segment is another conditional query which subsumes multiple base personality types, if present, to a new personality.
NEXT inside a conditional query using {}
If a conditional query has a NEXT in any of its THEN or ELSE blocks, it is necessary to wrap the part after THEN or ELSE with {}.
NEXT inside conditional queryMATCH (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 customer AS customer, sum * (1 - customer.discount) AS finalSum
NEXT
RETURN customer.firstName AS customer, "club below 1000" AS customerType, finalSum AS sum
}
| customer | customerType | sum |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 3 |
||
The query above calculates the total price of products purchased per customer and then only applies the customer discount to sums below 1000.
Known issues and fixes
NEXT initially did not correctly handle aggregations in the context of UNION branches and CALL subqueries.
As of Neo4j 2025.08, this issue has been fixed.
The table below summarizes the behavior changes across versions.
| Neo4j version | Behavior |
|---|---|
2025.06 |
|
2025.07 |
Using aggregations with |
2025.08+ |
|