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 @@ +BOUGHTdate:DATESUPPLIESCustomerfirstName:STRINGlastName:STRINGemail:STRINGdiscount:FLOATProductname:STRINGprice:INTEGERSuppliername:STRINGemail:STRING \ 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]