Sequential queries (NEXT
)
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:
-
NEXT
can improve the modularity and readability of complex queries. -
NEXT
can be used instead of WITH clause to construct complex queries. -
NEXT
can improve the usability of Conditional queries (WHEN
). -
NEXT
allows 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:
NEXT
MATCH (c:Customer)
RETURN c AS customer
NEXT
MATCH (customer)-[:BUYS]->(:Product {name: 'Chocolate'})
RETURN customer.firstName AS chocolateCustomer
chocolateCustomer |
---|
|
|
|
Rows: 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 |
---|---|
|
|
|
|
|
|
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:
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
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 NEXT
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 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 NEXT
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
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 CALL
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 |
---|---|
|
|
|
|
|
|
|
|
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.
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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+ |
|