From 2c81cf1d91be693a3181619ed0afff3d56b09503 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?=
<112686610+JPryce-Aklundh@users.noreply.github.com>
Date: Tue, 8 Apr 2025 11:15:34 +0200
Subject: [PATCH 1/7] filtering and sequencing left
---
modules/ROOT/images/with_clause.svg | 1 +
modules/ROOT/pages/clauses/with.adoc | 386 ++++++++++++++++++++++++++-
2 files changed, 385 insertions(+), 2 deletions(-)
create mode 100644 modules/ROOT/images/with_clause.svg
diff --git a/modules/ROOT/images/with_clause.svg b/modules/ROOT/images/with_clause.svg
new file mode 100644
index 000000000..f77938445
--- /dev/null
+++ b/modules/ROOT/images/with_clause.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index 42b4cd568..db9d7f8de 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -1,8 +1,390 @@
-:description: The `WITH` clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next.
+:description: Information about Cypher's `WITH` clause, which allows query parts to be chained together, piping the results from one part to be used as the starting point of the next.
-[[query-with]]
= WITH
+The `WITH` clause allows query parts to be chained together, piping the results from one part to be used as the starting points for the next.
+
+[[example-graph]]
+== Example graph
+A graph with the following schema is used for the examples below:
+
+image::with_clause.svg[width="600",role="middle"]
+
+To recreate the graph, run the following query against an empty Neo4j database.
+
+[source, cypher, role=test-setup]
+----
+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]]
+== Create new variables
+
+`WITH` can be used to create new variables if combined with `AS`, which can then be passed on to subsequent clauses.
+
+.Create a new variable
+[source, cypher]
+----
+WITH [1, 2, 3] AS list
+RETURN list
+----
+
+.Result
+[role="queryresult",options="header,footer",cols="1*(:Product {name: 'Chocolate'})
+WITH c AS customer
+RETURN customer.firstName AS chocolateCustomer
+----
+
+.Result
+[role="queryresult",options="header,footer",cols="1*(p:Product {name: 'Chocolate'})
+WITH c.name AS chocolateCustomers
+RETURN chocolateCustomers,
+ p.price AS chocolatePrice
+----
+
+.Error message
+[source, error]
+----
+Variable `p` not defined
+----
+
+[NOTE]
+The `WHERE` subclause is not limited in the same way by preceding `WITH` clauses.
+For more information, see xref:clauses/where.adoc#where-and-with[Using `WHERE` after `WITH`].
+
+To retain all variables in the scope of the query, use `WITH *`.
+
+.Retain all variables with `WITH *`
+[source, cypher]
+----
+MATCH (supplier:Supplier)-[r]->(product:Product)
+WITH *
+RETURN s.name AS company, type(r) AS relType, product.name AS product
+----
+
+.Result
+[role="queryresult",options="header,footer",cols="3*(chocolate:Product {name: 'Chocolate'})
+WITH customer.firstName || ' ' || customer.lastName AS customerFullName,
+ chocolate.price * (1 - customer.discount) AS chocolateNetPrice
+RETURN customerFullName, chocolateNetPrice
+----
+
+.Result
+[role="queryresult",options="header,footer",cols="2*= 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
+[role="queryresult",options="header,footer", cols="4* Chaining expressions].
+
+[[aggregations]]
+== Aggregations
+
+The `WITH` clause can perform aggregations and bind the results to new variables.
+In this example, the xref:functions/aggregating.adoc#functions-sum[`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 xref:functions/aggregating.adoc#functions-collect[`collect()`] is used to collect each product into `LIST` values bound to the `productsBought` variable.
+
+.`WITH` performing aggregations
+[source, cypher]
+----
+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
+[role="queryresult",options="header,footer", cols="3*(p:Product)
+WITH c, sum(p.price) AS totalSpent
+ORDER BY totalSpent DESC
+RETURN c.firstName AS customer, totalSpent
+----
+
+.Result
+[role="queryresult",options="header,footer", cols="2*(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
+----
+
+[role="queryresult",options="header,footer", cols="3*(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
+----
+
+[role="queryresult",options="header,footer", cols="3*
Date: Thu, 10 Apr 2025 13:26:14 +0200
Subject: [PATCH 2/7] update
---
modules/ROOT/pages/clauses/with.adoc | 395 ++++++++++-----------------
1 file changed, 144 insertions(+), 251 deletions(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index db9d7f8de..022d249b7 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -1,8 +1,18 @@
:description: Information about Cypher's `WITH` clause, which allows query parts to be chained together, piping the results from one part to be used as the starting point of the next.
+:table-caption!:
= WITH
-The `WITH` clause allows query parts to be chained together, piping the results from one part to be used as the starting points for the next.
+The `WITH` clause serves multiple purposes in Cypher:
+
+* xref:clauses/with.adoc#create-new-variables[Create new variables]
+* xref:clauses/with.adoc#variable-scope[Control variables in scope]
+* xref:clauses/with.adoc#bind-values-to-variables[Bind the results of expressions to new variables]
+* xref:clauses/with.adoc#aggregations[Perform aggregations]
+* xref:clauses/with.adoc#remove-duplicate-values[Remove duplicate values]
+* xref:clauses/with.adoc#ordering-and-pagination[Order and paginate results]
+* xref:clauses/with.adoc#filter-results[Filter results]
+* xref:clauses/with.adoc#linear-query-composition[Enable linear composition of multiple queries]
[[example-graph]]
== Example graph
@@ -56,7 +66,7 @@ CREATE (techCorp:Supplier {name: 'TechCorp', email: 'contact@techcorp.com'}),
[[create-new-variables]]
== Create new variables
-`WITH` can be used to create new variables if combined with `AS`, which can then be passed on to subsequent clauses.
+`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
[source, cypher]
@@ -75,7 +85,7 @@ RETURN list
1+d|Rows: 1
|===
-In this example, the `WITH` clause binds all matched `Customer` nodes to a new variable, `chocolateCustomers`.
+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
@@ -98,13 +108,18 @@ RETURN customer.firstName AS chocolateCustomer
1+d|Rows: 3
|===
-== Variable scope
+[[variable-scope]]
+== Control variables in scope
-If a variable is not explicitly referenced in a `WITH` clause, it is dropped from the scope of the query.
-That means it cannot be referenced by subsequent clauses.
+`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
[source, cypher, role=test-fail]
@@ -125,14 +140,14 @@ Variable `p` not defined
The `WHERE` subclause is not limited in the same way by preceding `WITH` clauses.
For more information, see xref:clauses/where.adoc#where-and-with[Using `WHERE` after `WITH`].
-To retain all variables in the scope of the query, use `WITH *`.
-
.Retain all variables with `WITH *`
[source, cypher]
----
MATCH (supplier:Supplier)-[r]->(product:Product)
WITH *
-RETURN s.name AS company, type(r) AS relType, product.name AS product
+RETURN s.name AS company,
+ type(r) AS relType,
+ product.name AS product
----
.Result
@@ -161,7 +176,8 @@ In the below query, the value of the xref:expressions/string-operators.adoc[`STR
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
+RETURN customerFullName,
+ chocolateNetPrice
----
.Result
@@ -170,14 +186,13 @@ RETURN customerFullName, chocolateNetPrice
| customerFullName | chocolateNetPrice
| "Amir Rahman" | 4.5
-| "Mateo Ortega" | 4.75
+| "Mateo Ortega" | 4.75
| "Yusuf Abdi" | 4.5
2+d|Rows: 3
|===
-Several `WITH` clauses can be used to chain expressions.
-Note that each variable must be explicitly referenced in each subsequent `WITH` clause in order for it to be available in the final `RETURN`.
+Because `WITH` can be used to assign variables to the values of expressions, it can be used to chain expressions.
.Chain expressions using `WITH`
[source, cypher]
@@ -222,7 +237,7 @@ For more information, see xref:clauses/let.adoc#chaining-expressions[`LET` -> Ch
The `WITH` clause can perform aggregations and bind the results to new variables.
In this example, the xref:functions/aggregating.adoc#functions-sum[`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 xref:functions/aggregating.adoc#functions-collect[`collect()`] is used to collect each product into `LIST` values bound to the `productsBought` variable.
+The xref:functions/aggregating.adoc#functions-collect[`collect()`] function is used to collect each product into `LIST` values bound to the `productsBought` variable.
.`WITH` performing aggregations
[source, cypher]
@@ -231,7 +246,9 @@ 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
+RETURN customer,
+ totalSpent,
+ productsBought
ORDER BY totalSpent DESC
----
@@ -251,7 +268,7 @@ ORDER BY totalSpent DESC
3+d|Rows: 7
|===
-[[remove-duplicates]]
+[[remove-duplicate-values]]
== Remove duplicate values
`WITH` can be used to remove duplicate values from the result set if appended with the modifier `DISTINCT`.
@@ -262,38 +279,70 @@ In the below query, `WITH DISTINCT` is used to remove any duplicate `discount` p
[source, cypher]
----
MATCH (c:Customer)
-WITH DISTINCT c.discount AS discountRate,
- collect(c.name) AS customers,
-RETURN customers, discountRate
-ORDER BY discountRate
+WITH DISTINCT c.discount AS discountRates
+RETURN discountRates
+ORDER BY discountRates
----
.Result
[role="queryresult",options="header,footer", cols="2*(p:Product)
-WITH c, sum(p.price) AS totalSpent
+WITH c,
+ sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
RETURN c.firstName AS customer, totalSpent
----
@@ -314,14 +363,15 @@ RETURN c.firstName AS customer, totalSpent
2+d|Rows: 7
|===
-In the next example, xref:clauses/limit.adoc[`LIMIT`] is used to only retain the top 3 customers with the highest `totalSpent` values in the result set after ordering
+In the next example, xref:clauses/limit.adoc[`LIMIT`] is used to only retain the top 3 customers with the highest `totalSpent` values in the result set after ordering.
Then, the xref:clauses/set.adoc[`SET`] assigns a new property (`topSpender = true`) to those customers who have spent the most.
-.Limiting results with `LIMIT`
+.Limit results with `LIMIT`
[source, cypher]
----
MATCH (c:Customer)-[:BUYS]->(p:Product)
-WITH c, sum(p.price) AS totalSpent
+WITH c,
+ sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
LIMIT 3
SET c.topSpender = true
@@ -341,15 +391,15 @@ RETURN c.firstName AS customer,
3+d|Rows: 3
|===
-xref:clauses/skip.adoc[`SKIP`] can be used to discard rows from the result set.
-Her
-The SKIP clause is used to exclude the top 3 customers (the first 3 rows in the sorted result set). After skipping these top spenders, the SET clause assigns a new property (topSpender = false) to the remaining customers,
+xref:clauses/skip.adoc[`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.
-.Skipping results with `SKIP`
+.Exclude results with `SKIP`
[source, cypher]
----
MATCH (c:Customer)-[:BUYS]->(p:Product)
-WITH c, sum(p.price) AS totalSpent
+WITH c,
+ sum(p.price) AS totalSpent
ORDER BY totalSpent DESC
SKIP 3
SET c.topSpender = false
@@ -370,249 +420,92 @@ RETURN c.firstName AS customer,
3+d|Rows: 4
|===
-== Filtering
-
-
-== Sequential
-
-
-
-
-
-
-
-
-
-
-
-The `WITH` clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next.
-
-[NOTE]
-====
-It is important to note that `WITH` affects variables in scope.
-Any variables not included in the `WITH` clause are not carried over to the rest of the query.
-The wildcard `*` can be used to include all variables that are currently in scope.
-====
-
-Using `WITH`, you can manipulate the output before it is passed on to the following query parts.
-Manipulations can be done to the shape and/or number of entries in the result set.
-
-One common usage of `WITH` is to limit the number of entries passed on to other `MATCH` clauses.
-By combining `ORDER BY` and `LIMIT`, it is possible to get the top X entries by some criteria and then bring in additional data from the graph.
-
-`WITH` can also be used to introduce new variables containing the results of expressions for use in the following query parts (see xref::clauses/with.adoc#with-introduce-variables[Introducing variables for expressions]).
-For convenience, the wildcard `*` expands to all variables that are currently in scope and carries them over to the next query part (see xref::clauses/with.adoc#with-wildcard[Using the wildcard to carry over variables]).
-
-Another use is to filter on aggregated values.
-`WITH` is used to introduce aggregates which can then be used in predicates in `WHERE`.
-These aggregate expressions create new bindings in the results.
-
-image:graph_with_clause.svg[]
-
-////
-[source, cypher, role=test-setup]
-----
-CREATE
- (a {name: 'Anders'}),
- (b {name: 'Bossman'}),
- (c {name: 'Caesar'}),
- (d {name: 'David'}),
- (e {name: 'George'}),
- (a)-[:KNOWS]->(b),
- (a)-[:BLOCKS]->(c),
- (d)-[:KNOWS]->(a),
- (b)-[:KNOWS]->(e),
- (c)-[:KNOWS]->(e),
- (b)-[:BLOCKS]->(d)
-----
-////
-
-
-[[with-introduce-variables]]
-== Introducing variables for expressions
+`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.
-You can introduce new variables for the result of evaluating expressions.
-
-.Query
-[source, cypher, indent=0]
+.Control pattern matching scope with ordering and pagination
+[source, cypher]
----
-MATCH (george {name: 'George'})<--(otherPerson)
-WITH otherPerson, toUpper(otherPerson.name) AS upperCaseName
-WHERE upperCaseName STARTS WITH 'C'
-RETURN otherPerson.name
+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
----
-This query returns the name of persons connected to *'George'* whose name starts with a `C`, regardless of capitalization.
-.Result
-[role="queryresult",options="header,footer",cols="1*(otherPerson)
-WITH *, type(r) AS connectionType
-RETURN person.name, otherPerson.name, connectionType
-----
-
-This query returns the names of all related persons and the type of relationship between them.
-
-.Result
-[role="queryresult",options="header,footer",cols="3*()
-WITH otherPerson, count(*) AS foaf
-WHERE foaf > 1
-RETURN otherPerson.name
+UNWIND [1, 2, 3, 4, 5, 6] AS x
+WITH x
+WHERE x > 2
+RETURN x
----
-The name of the person connected to *'David'* with the at least more than one outgoing relationship will be returned by the query.
-
-.Result
-[role="queryresult",options="header,footer",cols="1*` construct is 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.
-You can match paths, limit to a certain number, and then match again using those paths as a base, as well as any number of similar limited searches.
-
-.Query
-[source, cypher, indent=0]
+.Filter property values using `WITH` and `WHERE`
+[source, cypher]
----
-MATCH (n {name: 'Anders'})--(m)
-WITH m
-ORDER BY m.name DESC
-LIMIT 1
-MATCH (m)--(o)
-RETURN o.name
+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
----
-Starting at *'Anders'*, find all matching nodes, order by name descending and get the top result, then find all the nodes connected to that top result, and return their names.
-
-.Result
-[role="queryresult",options="header,footer",cols="1* 2
-RETURN x
-----
-
-The limit is first applied, reducing the rows to the first 5 items in the list. The filter is then applied, reducing the final result as seen below:
-
-.Result
-[role="queryresult",options="header,footer",cols="1*` constructs.
+For more information, see xref:clauses/filter.adoc#filter-with-where[`FILTER` as a substitute for `WITH * WHERE`].
-Query
-[source, cypher, indent=0]
-----
-UNWIND [1, 2, 3, 4, 5, 6] AS x
-WITH x
-WHERE x > 2
-WITH x
-LIMIT 5
-RETURN x
-----
+[[linear-query-composition]]
+== Linear composition of multiple queries
-This time the filter is applied first, reducing the rows to consist of the list `[3, 4, 5, 6]`.
-Then the limit is applied.
-As the limit is larger than the total number of remaining rows, all rows are returned.
-.Result
-[role="queryresult",options="header,footer",cols="1*
Date: Thu, 10 Apr 2025 13:46:08 +0200
Subject: [PATCH 3/7] some cleanup
---
modules/ROOT/pages/clauses/with.adoc | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index 022d249b7..2c17c6121 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -168,7 +168,7 @@ RETURN s.name AS company,
== Bind values to variables
`WITH` can be used to assign the values of expressions to variables.
-In the below query, the value of the xref:expressions/string-operators.adoc[`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.
+In the below query, the value of the xref:expressions/string-operators.adoc[`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
[source, cypher]
@@ -257,7 +257,7 @@ ORDER BY totalSpent DESC
|===
| customer | totalSpent | productsBought
-| "Mateo" | 1015 ["Laptop", "Chocolate", "Coffee"]
+| "Mateo" | 1015 | ["Laptop", "Chocolate", "Coffee"]
| "Amir" | 1005 | ["Laptop", "Chocolate"]
| "Yusuf" | 1005 | ["Laptop", "Chocolate"]
| "Leila" | 1000 | ["Laptop"]
@@ -475,7 +475,7 @@ RETURN x
1+d|Rows: 4
|===
-In the below query, the `WITH * WHERE ` construct is used to filter out any `Supplier` nodes whose `totalSales` is less than `1000`.
+In the below query, the `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`
From 555416fee8a25da76442a7f3b9c9bd284ecf5035 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?=
<112686610+JPryce-Aklundh@users.noreply.github.com>
Date: Thu, 10 Apr 2025 13:52:08 +0200
Subject: [PATCH 4/7] fix
---
modules/ROOT/pages/clauses/with.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index 2c17c6121..83c0154d3 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -285,7 +285,7 @@ ORDER BY discountRates
----
.Result
-[role="queryresult",options="header,footer", cols="2*
Date: Fri, 11 Apr 2025 15:37:32 +0200
Subject: [PATCH 5/7] explain subqueries
---
modules/ROOT/pages/clauses/with.adoc | 38 +++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index 83c0154d3..a93f682ec 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -12,7 +12,6 @@ The `WITH` clause serves multiple purposes in Cypher:
* xref:clauses/with.adoc#remove-duplicate-values[Remove duplicate values]
* xref:clauses/with.adoc#ordering-and-pagination[Order and paginate results]
* xref:clauses/with.adoc#filter-results[Filter results]
-* xref:clauses/with.adoc#linear-query-composition[Enable linear composition of multiple queries]
[[example-graph]]
== Example graph
@@ -164,6 +163,36 @@ RETURN s.name AS company,
3+d|Rows: 5
|===
+`WITH` cannot de-scope variables imported to a xref:subqueries/call-subquery.adoc[`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
+[source, cypher]
+----
+WITH 11 AS x
+CALL (x) {
+ UNWIND [2, 3] AS y
+ WITH y
+ RETURN x*y AS a
+}
+RETURN x, a
+----
+
+.Result
+[role="queryresult",options="header,footer",cols="2* Import variables].
+
[[bind-values-to-variables]]
== Bind values to variables
@@ -303,7 +332,7 @@ ORDER BY discountRates
== Explicitly project values
`WITH ALL` can be used to explicitly project all values bound to a variable.
-This functionality was introduced as part of Cypher's xref:appendix/gql-conformance/index.adoc[], and using it is functionally the same as using simple `WITH`.
+Using it is functionally the same as using simple `WITH`.
.Explicit result projection using `WITH ALL`
[source, cypher]
@@ -504,8 +533,3 @@ RETURN s.name AS supplier,
[NOTE]
The `FILTER` clause can be used as a more concise alternative to `WITH * WHERE ` constructs.
For more information, see xref:clauses/filter.adoc#filter-with-where[`FILTER` as a substitute for `WITH * WHERE`].
-
-[[linear-query-composition]]
-== Linear composition of multiple queries
-
-
From 6c33b4064792761090b18e65ba0d269f03a063a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?=
<112686610+JPryce-Aklundh@users.noreply.github.com>
Date: Mon, 14 Apr 2025 10:21:35 +0200
Subject: [PATCH 6/7] subclause clarification and formating
---
modules/ROOT/pages/clauses/with.adoc | 41 +++++++++++++---------------
1 file changed, 19 insertions(+), 22 deletions(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index a93f682ec..b93384de3 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -135,10 +135,6 @@ RETURN chocolateCustomers,
Variable `p` not defined
----
-[NOTE]
-The `WHERE` subclause is not limited in the same way by preceding `WITH` clauses.
-For more information, see xref:clauses/where.adoc#where-and-with[Using `WHERE` after `WITH`].
-
.Retain all variables with `WITH *`
[source, cypher]
----
@@ -256,9 +252,7 @@ ORDER BY price
|===
[NOTE]
-The `LET` clause can also be used to assign values to variables.
-However, unlike `WITH`, it cannot drop variables from the query scope.
-As a result, `LET` can be used to chain expressions in a more clear and concise manner than `WITH`.
+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 xref:clauses/let.adoc#chaining-expressions[`LET` -> Chaining expressions].
[[aggregations]]
@@ -362,9 +356,10 @@ ORDER BY discountRates
[[ordering-pagination]]
== Ordering and pagination
-`WITH` can order and paginate results before they are passed on to subsequent clauses.
+`WITH` can order and paginate results if used together with the xref:clauses/order-by.adoc[`ORDER BY`], xref:clauses/limit.adoc[`LIMIT`], and xref:clauses/skip.adoc[`SKIP`] subclauses.
+If so, these subclauses be understood as part of the result manipulation performed by `WITH` -- not as a standalone clause -- 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 xref:clauses/order-by.adoc[`ORDER BY`] before they are passed on to the final `RETURN` clause.
+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`
[source, cypher]
@@ -372,7 +367,7 @@ In the below query, the results are ordered in a descending order by which `Cust
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
-ORDER BY totalSpent DESC
+ ORDER BY totalSpent DESC
RETURN c.firstName AS customer, totalSpent
----
@@ -392,7 +387,7 @@ RETURN c.firstName AS customer, totalSpent
2+d|Rows: 7
|===
-In the next example, xref:clauses/limit.adoc[`LIMIT`] is used to only retain the top 3 customers with the highest `totalSpent` values in the result set after ordering.
+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 xref:clauses/set.adoc[`SET`] assigns a new property (`topSpender = true`) to those customers who have spent the most.
.Limit results with `LIMIT`
@@ -401,8 +396,8 @@ Then, the xref:clauses/set.adoc[`SET`] assigns a new property (`topSpender = tru
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
-ORDER BY totalSpent DESC
-LIMIT 3
+ ORDER BY totalSpent DESC
+ LIMIT 3
SET c.topSpender = true
RETURN c.firstName AS customer,
totalSpent,
@@ -420,7 +415,7 @@ RETURN c.firstName AS customer,
3+d|Rows: 3
|===
-xref:clauses/skip.adoc[`SKIP`] can be used after a `WITH` clause to discard rows from the result set.
+`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`
@@ -429,8 +424,8 @@ Below, `SKIP` excludes the first 3 rows in the ordered result set (i.e. the 3 `C
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
sum(p.price) AS totalSpent
-ORDER BY totalSpent DESC
-SKIP 3
+ ORDER BY totalSpent DESC
+ SKIP 3
SET c.topSpender = false
RETURN c.firstName AS customer,
totalSpent,
@@ -459,8 +454,8 @@ The second `MATCH` clause then matches only from that single product to find all
----
MATCH (:Supplier {name: 'Foodies Inc.'})-[:SUPPLIES]->(p:Product)
WITH p
-ORDER BY p.price DESC
-LIMIT 1
+ ORDER BY p.price DESC
+ LIMIT 1
MATCH (p)<-[:BUYS]-(c:Customer)
RETURN p.name AS product
p.price AS price,
@@ -481,14 +476,16 @@ RETURN p.name AS product
[[filter-results]]
== Filter results
-`WITH` can be used in conjunction with `WHERE` to filter results.
+`WITH` can be followed by the xref:clauses/where.adoc[`WHERE`] subclause to filter results.
+Similar to the subclauses used for xref:clauses/with.adoc#ordering-pagination[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 xref:clauses/where.adoc#where-and-with[Using `WHERE` after `WITH`].
.Filter using `WITH` and `WHERE`
[source, cypher]
----
UNWIND [1, 2, 3, 4, 5, 6] AS x
WITH x
-WHERE x > 2
+ WHERE x > 2
RETURN x
----
@@ -504,7 +501,7 @@ RETURN x
1+d|Rows: 4
|===
-In the below query, the `WITH` and `WHERE` are used to filter out any `Supplier` nodes whose `totalSales` is less than `1000`.
+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`
@@ -514,7 +511,7 @@ MATCH (s:Supplier)-[:SUPPLIES]->(p:Product)<-[:BUYS]-(c:Customer)
WITH s,
sum(p.price) AS totalSales,
count(DISTINCT c) AS uniqueCustomers
-WHERE totalSales > 1000
+ WHERE totalSales > 1000
RETURN s.name AS supplier,
totalSales,
uniqueCustomers
From 155efbe94d73d606e4aac6e8b4276c63e24d96d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?=
<112686610+JPryce-Aklundh@users.noreply.github.com>
Date: Mon, 14 Apr 2025 11:49:09 +0200
Subject: [PATCH 7/7] edits
---
modules/ROOT/pages/clauses/with.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc
index b93384de3..370c14ac1 100644
--- a/modules/ROOT/pages/clauses/with.adoc
+++ b/modules/ROOT/pages/clauses/with.adoc
@@ -118,7 +118,7 @@ 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.
+Nor would the `c` variable be available -- only `chocolateCustomers` is available due to the preceding `WITH` clause.
.De-scoping a variable
[source, cypher, role=test-fail]