WITH

Example graph

A graph with the following schema is used for the examples below:

with clause

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.

Create a new variable
WITH [1, 2, 3] AS list
RETURN list
Result
list

[1, 2, 3]

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.

Create a new variable bound to matched nodes
MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Chocolate'})
WITH c AS customer
RETURN customer.firstName AS chocolateCustomer
Result
chocolateCustomer

"Amir"

"Mateo"

"Yusuf"

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.

De-scoping a variable
MATCH (c:Customer)-[:BUYS]->(p:Product {name: 'Chocolate'})
WITH c.name AS chocolateCustomers
RETURN chocolateCustomers,
       p.price AS chocolatePrice
Error message
Variable `p` not defined
Retain all variables with WITH *
MATCH (supplier:Supplier)-[r]->(product:Product)
WITH *
RETURN supplier.name AS company,
       type(r) AS relType,
       product.name AS product
Result
company relType product

"TechCorp"

"SUPPLIES"

"Laptop"

"TechCorp"

"SUPPLIES"

"Phone"

"TechCorp"

"SUPPLIES"

"Headphones"

"Foodies Inc."

"SUPPLIES"

"Chocolate"

"Foodies Inc."

"SUPPLIES"

"Coffee"

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.

Variables cannot be de-scoped in the inner scope of a subquery
WITH 11 AS x
CALL (x) {
  UNWIND [2, 3] AS y
  WITH y
  RETURN x*y AS a
}
RETURN x, a
Result
x a

11

22

11

33

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.

Bind values to variables
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
Result
customerFullName chocolateNetPrice

"Amir Rahman"

4.5

"Mateo Ortega"

4.75

"Yusuf Abdi"

4.5

Rows: 3

Because WITH can be used to assign variables to the values of expressions, it can be used to chain expressions.

Chain expressions using 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
Result
product price isAffordable discountCategory

"Chocolate"

5

TRUE

'Budget'

"Coffee"

10

TRUE

'Budget'

"Headphones"

250

TRUE

'Budget'

"Phone"

500

FALSE

'High-end'

"Laptop"

1000

FALSE

'High-end'

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 aggregations
MATCH (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
Result
customer totalSpent productsBought

"Mateo"

1015

["Laptop", "Chocolate", "Coffee"]

"Amir"

1005

["Laptop", "Chocolate"]

"Yusuf"

1005

["Laptop", "Chocolate"]

"Leila"

1000

["Laptop"]

"Niko"

760

["Phone", "Headphones", "Coffee"]

"Hannah"

260

["Headphones", "Coffee"]

"Keisha"

250

["Headphones"]

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 values
MATCH (c:Customer)
WITH DISTINCT c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
Result
discountRates

0.05

0.1

0.15

0.2

0.25

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.

Explicit result projection using WITH ALL
MATCH (c:Customer)
WITH ALL c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
Result
discountRates

0.05

0.1

0.1

0.1

0.15

0.2

0.25

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 results with 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
Result
customer totalSpent

"Mateo"

1015

"Amir"

1005

"Yusuf"

1005

"Leila"

1000

"Niko"

760

"Hannah"

260

"Keisha"

250

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 results with 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

"Mateo"

1015

TRUE

"Amir"

1005

TRUE

"Yusuf"

1005

TRUE

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.

Exclude results with 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

"Leila"

1000

FALSE

"Niko"

760

FALSE

"Hannah"

260

FALSE

"Keisha"

250

FALSE

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.

Control pattern matching scope with ordering and pagination
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

"Coffee"

10

["Mateo", "Hannah", "Niko"]

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.

Filter using WITH and WHERE
UNWIND [1, 2, 3, 4, 5, 6] AS x
WITH x
  WHERE x > 2
RETURN x
x

3

4

5

6

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.

Filter property values using 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

"TechCorp"

5250

7

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.