LET
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 WITH
Any variable not explicitly referenced by WITH
(or carried over by WITH *
) is dropped from the scope of subsequent clauses.
WITH
MATCH (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 WITH
The below query shows that variables bound by a LET
clause 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 LET
MATCH (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 LET
MATCH (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 |