WITH
The WITH
clause serves multiple purposes in Cypher®:
Example graph
A graph with the following schema is used for the examples below:
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)
Create new variables
WITH
can be used in combination with the AS
keyword to bind new variables which can then be passed to subsequent clauses.
WITH [1, 2, 3] AS list
RETURN list
list |
---|
|
Rows: 1 |
In the below example, the WITH
clause binds all matched Customer
nodes to a new variable, chocolateCustomers
.
The bound nodes are MAP
values which can then be referenced from the new variable.
MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Chocolate'})
WITH c AS customer
RETURN customer.firstName AS chocolateCustomer
chocolateCustomer |
---|
|
|
|
Rows: 3 |
Control variables in scope
WITH
can be used to control which variables remain within the scope of a query.
Any variable that is referenced by a WITH
clause remains with the scope of the query and is available to subsequent clauses.
If a variable is re-named in a WITH
clause, it can only be referenced by its new name by subsequent clauses.
If a variable is not explicitly referenced in a WITH
clause, it is dropped from the scope of the query and cannot be referenced by subsequent clauses.
To retain all variables in the scope of the query, use WITH *
.
In the below query, the WITH
clause de-scopes the p
variable.
As a result, it is not available to the subsequent RETURN
clause.
Nor would the c
variable be available — only chocolateCustomers
is available due to the preceding WITH
clause.
MATCH (c:Customer)-[:BUYS]->(p:Product {name: 'Chocolate'})
WITH c.name AS chocolateCustomers
RETURN chocolateCustomers,
p.price AS chocolatePrice
Variable `p` not defined
WITH *
MATCH (supplier:Supplier)-[r]->(product:Product)
WITH *
RETURN supplier.name AS company,
type(r) AS relType,
product.name AS product
company | relType | product |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
WITH
cannot de-scope variables imported to a CALL
subquery, because variables imported to a subquery are considered global to its inner scope.
More specifically, a variable imported into a CALL
subquery will be available to subsequent clauses even if a preceding WITH
clause does not reference it.
In the below example, the x
variable is imported to the inside scope of a CALL
subquery, and is successfully referenced by the RETURN
clause even though the preceding WITH
neglects to list it.
WITH 11 AS x
CALL (x) {
UNWIND [2, 3] AS y
WITH y
RETURN x*y AS a
}
RETURN x, a
x | a |
---|---|
|
|
|
|
Rows: 2 |
For more information, see CALL
subqueries → Import variables.
Bind values to variables
WITH
can be used to assign the values of expressions to variables.
In the below query, the value of the STRING
concatenation expression is bound to a new variable customerFullName
, and the value from the expression chocolate.price * (1 - customer.discount)
is bound to chocolateNetPrice
, both of which are then available in the RETURN
clause.
MATCH (customer:Customer)-[:BUYS]->(chocolate:Product {name: 'Chocolate'})
WITH customer.firstName || ' ' || customer.lastName AS customerFullName,
chocolate.price * (1 - customer.discount) AS chocolateNetPrice
RETURN customerFullName,
chocolateNetPrice
customerFullName | chocolateNetPrice |
---|---|
|
|
|
|
|
|
Rows: 3 |
Because WITH
can be used to assign variables to the values of expressions, it can be used to chain expressions.
WITH
MATCH (p:Product)
WITH p, p.price >= 500 AS isExpensive
WITH p, isExpensive, NOT isExpensive AS isAffordable
WITH p, isExpensive, isAffordable,
CASE
WHEN isExpensive THEN 'High-end'
ELSE 'Budget'
END AS discountCategory
RETURN p.name AS product,
p.price AS price,
isAffordable,
discountCategory
ORDER BY price
product | price | isAffordable | discountCategory |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
The LET clause can be used to assign values to variables and to chain expressions more clearly and concisely than WITH .
For more information, see LET → Chaining expressions.
|
Aggregations
The WITH
clause can perform aggregations and bind the results to new variables.
In this example, the sum()
function is used to calculate the total spent by each customer, and the value for each is bound to the new variable totalSpent
.
The collect()
function is used to collect each product into LIST
values bound to the productsBought
variable.
WITH
performing aggregationsMATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c.firstName AS customer,
sum(p.price) AS totalSpent,
collect(p.name) AS productsBought
RETURN customer,
totalSpent,
productsBought
ORDER BY totalSpent DESC
customer | totalSpent | productsBought |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 7 |
Remove duplicate values
WITH
can be used to remove duplicate values from the result set if appended with the modifier DISTINCT
.
In the below query, WITH DISTINCT
is used to remove any duplicate discount
property values from Customer
nodes.
WITH DISTINCT
to remove duplicate valuesMATCH (c:Customer)
WITH DISTINCT c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
discountRates |
---|
|
|
|
|
|
Rows: 5 |
Explicitly project values
WITH ALL
can be used to explicitly project all values bound to a variable.
Using it is functionally the same as using simple WITH
.
WITH ALL
MATCH (c:Customer)
WITH ALL c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
discountRates |
---|
|
|
|
|
|
|
|
Rows: 7 |
Ordering and pagination
WITH
can order and paginate results if used together with the ORDER BY
, LIMIT
, and SKIP
subclauses.
If so, these subclauses should be understood as part of the result manipulation performed by WITH
— not as standalone clauses — before results are passed on to subsequent clauses.
In the below query, the results are ordered in a descending order by which Customer
has spent the most using ORDER BY
before they are passed on to the final RETURN
clause.
ORDER BY
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
RETURN c.firstName AS customer, totalSpent
customer | totalSpent |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 7 |
In the next example, LIMIT
is used to only retain the top 3 customers with the highest totalSpent
values in the result set after ordering.
Then, the SET
assigns a new property (topSpender = true
) to those customers who have spent the most.
LIMIT
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
LIMIT 3
SET c.topSpender = true
RETURN c.firstName AS customer,
totalSpent,
c.topSpender AS topSpender
customer | totalSpent | topSpender |
---|---|---|
|
|
|
|
|
|
|
|
|
Rows: 3 |
SKIP
can be used after a WITH
clause to discard rows from the result set.
Below, SKIP
excludes the first 3 rows in the ordered result set (i.e. the 3 Customer
nodes with highest totalSpent
value) and assigns a false
value to the new topSpender
property of the remaining Customer
nodes.
SKIP
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
SKIP 3
SET c.topSpender = false
RETURN c.firstName AS customer,
totalSpent,
c.topSpender AS topSpender
customer | totalSpent | topSpender |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 4 |
ORDER BY
, LIMIT
, and SKIP
can also be used after a WITH
clause to narrow down the set of rows before continuing with further pattern matching.
In the query below, all products supplied by Foodies Inc.
are matched first.
WITH
passes those products forward, ORDER BY
sorts them by descending price
, and LIMIT
retains only the most expensive one.
The second MATCH
clause then matches only from that single product to find all customers who bought it.
MATCH (:Supplier {name: 'Foodies Inc.'})-[:SUPPLIES]->(p:Product)
WITH p
ORDER BY p.price DESC
LIMIT 1
MATCH (p)<-[:BUYS]-(c:Customer)
RETURN p.name AS product,
p.price AS price,
collect(c.firstName) AS customers
product | price | customers |
---|---|---|
|
|
|
Rows: 1 |
Filter results
WITH
can be followed by the WHERE
subclause to filter results.
Similar to the subclauses used for ordering and pagination, WHERE
should be understood as part of the result manipulation performed by WITH
— not as a standalone clause — before the results are passed on to subsequent clauses.
For more information, see Using WHERE
after WITH
.
WITH
and WHERE
UNWIND [1, 2, 3, 4, 5, 6] AS x
WITH x
WHERE x > 2
RETURN x
x |
---|
|
|
|
|
Rows: 4 |
In the below query, WITH
and WHERE
are used to filter out any Supplier
nodes whose totalSales
is less than 1000
.
Note the use of DISTINCT
inside collect()
to remove any duplicate Customer
nodes.
WITH
and WHERE
MATCH (s:Supplier)-[:SUPPLIES]->(p:Product)<-[:BUYS]-(c:Customer)
WITH s,
sum(p.price) AS totalSales,
count(DISTINCT c) AS uniqueCustomers
WHERE totalSales > 1000
RETURN s.name AS supplier,
totalSales,
uniqueCustomers
supplier | totalSales | uniqueCustomers |
---|---|---|
|
|
|
Rows: 1 |
The FILTER clause can be used as a more concise alternative to WITH * WHERE <predicate> constructs.
For more information, see FILTER as a substitute for WITH * WHERE .
|