LETCypher 25 onlyIntroduced in Neo4j 2025.06
LET binds expressions to variables.
For queries involving several chained expressions, it can be a more succinct and readable alternative to WITH.
Unlike WITH, LET does not drop variables from the scope of subsequent clauses.
Nor can it be used for aggregations or in combination with DISTINCT; it can only be used to bind new variables.
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)
Bind values to variables
LET is used to bind variables to the results of expressions.
LET variable = expression, variable = expression
LET to bind a variableMATCH (c:Customer)
LET fullName = c.firstName + ' ' + c.lastName
RETURN fullName
| fullName |
|---|
|
|
|
|
|
|
|
Rows: 7 |
LET to bind several variablesMATCH (s:Supplier)-[:SUPPLIES]->(p:Product)
LET supplier = s.name, product = p.name
RETURN supplier, product
| supplier | product |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 4 |
|
Differences between LET and WITH
There are important differences between LET and WITH that can be divided into the following categories:
Variables in scope
LET does not drop variables from the scope of subsequent clauses, while WITH does.
As such, LET <variable> = <expression> is a substitute for WITH *, <expression> AS <variable>, not WITH <expression> AS <variable> (which would drop any variables present in the preceding clause not referenced in <expression>).
LET and WITHAny variable not explicitly referenced by WITH (or carried over by WITH *) is dropped from the scope of subsequent clauses.
WITHMATCH (s:Supplier)-[:SUPPLIES]->(p:Product)
WITH s.name AS supplier
RETURN supplier, p.name AS product
Variable `p` not defined
LET, however, cannot regulate which variables are in scope.
Replacing WITH with LET in the above query would, therefore, return results.
LET does not drop variablesMATCH (s:Supplier)-[:SUPPLIES]->(p:Product)
LET supplier = s.name
RETURN supplier, p.name AS product
| supplier | product |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
Chaining expressions
The fact that LET does not drop variables means that it can be used to chain expressions in a clear and concise manner, where variables bound in one LET clause can be referenced by subsequent clauses.
LET and WITHThe below query shows that variables bound by a LET clause can be referenced by subsequent clauses without being explicitly carried over.
Specifically, the variable isExpensive is created in the first LET clause and referenced again in the subsequent clauses.
Note also that the variable p, bound in the MATCH clause, is available in the final RETURN clause despite not being referenced in any of LET clauses.
LET referencing variables assigned in previous a LETMATCH (p:Product)
LET isExpensive = p.price >= 500
LET isAffordable = NOT isExpensive
LET discountCategory = CASE
WHEN isExpensive THEN 'High-end'
ELSE 'Budget'
END
RETURN p.name AS product, p.price AS price, isAffordable, discountCategory
ORDER BY price
| product | price | isAffordable | discountCategory |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|||
Using WITH, the same query would become less succinct, as WITH would have to explicitly carry over each variable in between clauses:
WITH equivalentMATCH (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
Aggregations and DISTINCT
Unlike WITH, LET cannot perform aggregations or be combined with DISTINCT.
For example, in the following query, WITH could not be replaced by LET:
WITH DISTINCT and aggregations on expressionsMATCH (c:Customer)-[:BUYS]->(p:Product)
WITH DISTINCT c, sum(p.price) AS totalSpent
RETURN c.firstName AS customer, totalSpent
| customer | totalSpent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 7 |
|
WITH and LETMATCH (c:Customer)-[:BUYS]->(p:Product)
WITH DISTINCT c, sum(p.price) AS totalSpent
LET fullName = c.firstName + ' ' + c.lastName
RETURN fullName, totalSpent
| customer | totalSpent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 7 |
|
Advanced examples
The following examples demonstrates how LET, and its ability to chain expressions, can be used in more advanced queries:
This example retrieves information about what Product a Customer has bought, and from what Supplier.
It then calculates the price after applying the discount and constructs a message for each purchase, including the fullName of each Customer and the effectivePrice of a Product after a discount, sent to the Supplier email.
This example highlights that LET does not drop variables.
All variables introduced in the MATCH and subsequent LET clauses are available in the final RETURN clause.
Customer purchase details, including discount informationMATCH (c:Customer)-[b:BUYS]->(p:Product)<--(s:Supplier)
LET fullname = c.firstName + ' ' + c.lastName,
effectivePrice = p.price * (1 - c.discount)
LET message = fullname + " bought " + p.name + " for $" + effectivePrice + " after a " + (c.discount * 100) + "% discount"
RETURN b.date AS date, message, s.email AS toSupplier
ORDER BY date
| date | message | toSupplier |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 14 |
||
The example calculates the customerRevenue for each Customer after applying their discount on each Product they bought. Customers are then categorized into three groups based on their total spending: Category A for those who spent more than 850, Category B for those who spent more than 350 but less than or equal to 850, and Category C for those who spent 350 or less.
Category C customers are excluded from the results using the FILTER clause, leaving only Category A and B customers eligible for a gift card.
The amount in the gift card is assigned based on the category, with with Category A receiving 20 and Category B receiving 10.
The details of the gift card are then sent to the email of the relevant customers.
This example highlights how LET can be used to succinctly chain expressions, and also that it cannot be used to perform aggregations.
MATCH (customer:Customer)-[bought:BUYS]->(product:Product)
LET effectivePrice = product.price * (1 - customer.discount)
WITH customer, bought, sum(effectivePrice) AS customerRevenue
LET category = CASE
WHEN customerRevenue > 850 THEN 'A'
WHEN customerRevenue > 350 THEN 'B'
ELSE 'C'
END
FILTER category <> 'C'
LET amount = CASE
WHEN category = 'A' THEN 20
WHEN category = 'B' THEN 10
END
LET message = {
type: 'giftcard',
addressee: customer.firstName + ' ' + customer.lastName,
amount: amount,
year: bought.date.year
}
RETURN message, customer.email AS toCustomer, customerRevenue
ORDER BY amount
| message | toCustomer | customerRevenue |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
||