Sequential queries (NEXT
)
NEXT
allows for linear composition of queries into a sequence of smaller, self-contained segments, passing the return values 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 CALL subqueries and the WITH clause to construct complex queries. -
NEXT
can improve the usability of conditionalWHEN
and combinedUNION
queries.
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
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 a query and which are not explicitly returned 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.
Interactions with CALL
subqueries
NEXT
can serve as a more readable alternative to CALL
subqueries.
MATCH (p:Product)
CALL (p) {
MATCH (c:Customer)-[:BUYS]->(p)
RETURN collect(c.firstName) AS customers
}
RETURN p.name as product, customers
MATCH (p:Product)
RETURN p
NEXT
MATCH (c:Customer)-[:BUYS]->(p)
RETURN collect(c.firstName) AS customers, p
NEXT
RETURN p.name as product, customers
product | customers |
---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
Even though the query which uses NEXT
has more lines, it is divided into three segments which are easy to read.
It also avoids the parentheses and indentation of the CALL
subquery.
|
Interactions with conditional queries
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 a 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.
Interactions with UNION
queries
NEXT
in a query using 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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
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 |