diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index a8188c351..572c68093 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -32,6 +32,31 @@ For more information, see xref:queries/select-version.adoc[]. | Feature | Details +a| +label:functionality[] +label:updated[] +[source, cypher] +---- +MATCH (p:Product) WHERE p.name <> "Coffee" +CALL (p) { + MATCH (p)<-[:BUYS]-(c:Customer)-[:BUYS]->(otherProduct) + RETURN c, otherProduct + NEXT + RETURN count(DISTINCT c) AS customers, 0 AS customersAlsoBuyingCoffee + UNION + FILTER otherProduct.name = "Coffee" + RETURN 0 as customers, count(DISTINCT c) AS customersAlsoBuyingCoffee + NEXT + RETURN max(customers) AS customers, max(customersAlsoBuyingCoffee) AS customersAlsoBuyingCoffee +} +RETURN p.name AS product, + round(toFloat(customersAlsoBuyingCoffee) * 100 / customers, 1) AS percentageOfCustomersAlsoBuyingCoffee + ORDER BY product +---- + +| `NEXT` now correctly supports aggregations in the context of `UNION` and `CALL`. +For more information, see xref:queries/composed-queries/sequential-queries.adoc#issues-fixes[Sequential queries (`NEXT`) > Known issues and fixes]. + a| label:functionality[] label:updated[] diff --git a/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc b/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc index d87c8dd59..7a1265c13 100644 --- a/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc @@ -3,24 +3,25 @@ :table-caption!: :page-role: new-2025.06 -`NEXT` allows for linear composition of queries into a sequence of smaller, self-contained segments, passing the return values from one segment to the next. +`NEXT` allows for linear composition of queries into a sequence of smaller, self-contained segments, passing the whole table of intermediate results from one segment to the next. `NEXT` has the following benefits: * `NEXT` can improve the modularity and readability of complex queries. -* `NEXT` can be used instead of xref:subqueries/call-subquery.adoc[] and the xref:clauses/with.adoc[] clause to construct complex queries. -* `NEXT` can improve the usability of xref:queries/composed-queries/conditional-queries.adoc[conditional `WHEN`] and xref:queries/composed-queries/combined-queries.adoc[combined `UNION`] queries. +* `NEXT` can be used instead of xref:clauses/with.adoc[] clause to construct complex queries. +* `NEXT` can improve the usability of xref:queries/composed-queries/conditional-queries.adoc[]. +* `NEXT` allows for passing the full table of intermediate results into the branches of a xref:queries/composed-queries/combined-queries.adoc[`UNION`]. [[example-graph]] == Example graph The following graph is used for the examples on this page: -image::sequential-queries-graph.svg[Example graph connecting suppliers, products, and customers,width=600,role=popup] +image::sequential-queries-graph.svg[Example graph connecting suppliers,products,and customers,width=600,role=popup] To recreate the graph, run the following query against an empty Neo4j database. -[source, cypher, role=test-setup] +[source,cypher,role=test-setup] ---- CREATE (techCorp:Supplier {name: 'TechCorp', email: 'contact@techcorp.com'}), (foodies:Supplier {name: 'Foodies Inc.', email: 'info@foodies.com'}), @@ -65,7 +66,7 @@ CREATE (techCorp:Supplier {name: 'TechCorp', email: 'contact@techcorp.com'}), == Syntax .`NEXT` syntax -[source, syntax] +[source,syntax] ---- @@ -81,10 +82,12 @@ NEXT [[passing-values]] == Passing values to subsequent queries +`NEXT` passes the result table of a query to the subsequent query. In the following example, `NEXT` passes the variable `customer` to the second query: .Passing a variable to another query via `NEXT` -[source, cypher] +// tag::sequential_queries_basic_example[] +[source,cypher] ---- MATCH (c:Customer) RETURN c AS customer @@ -94,6 +97,7 @@ NEXT MATCH (customer)-[:BUYS]->(:Product {name: 'Chocolate'}) RETURN customer.firstName AS chocolateCustomer ---- +// end::sequential_queries_basic_example[] .Result [role="queryresult",options="header,footer",cols="1*(p:Product {name: 'Chocolate'}) RETURN c AS customer, p AS product @@ -120,7 +122,6 @@ NEXT RETURN customer.firstName AS chocolateCustomer, product.price * (1 - customer.discount) AS chocolatePrice ---- -// end::sequential_queries_basic_example[] .Result [role="queryresult",options="header,footer",cols="2*(p) - RETURN collect(c.firstName) AS customers -} -RETURN p.name as product, customers +MATCH (c:Customer)-[:BUYS]->(p:Product) +RETURN c AS customer, p AS product + +NEXT + +RETURN product.name AS product, + COUNT(customer) AS numberOfCustomers ---- -====== -[.include-with-NEXT] -====== +// end::sequential_queries_aggregation[] + +.Result +[role="queryresult",options="header,footer",cols="2*(p:Product) +RETURN c, p NEXT -MATCH (c:Customer)-[:BUYS]->(p) -RETURN collect(c.firstName) AS customers, p +RETURN c.firstName AS name, COLLECT(p.price * (1 - c.discount)) AS purchases, "discounted price" AS type +UNION +RETURN c.firstName AS name, COLLECT(p.price) AS purchases, "real price" AS type NEXT -RETURN p.name as product, customers +RETURN * ORDER BY name, type +---- +// end::sequential_queries_union[] + +.Result +[role="queryresult",options="header,footer",cols="3*(:Product{name: "Laptop"}) +RETURN c.firstName AS customer +UNION ALL +MATCH (c:Customer)-[:BUYS]-> (:Product{name: "Coffee"}) +RETURN c.firstName AS customer + +NEXT + +RETURN customer AS customer, count(customer) as numberOfProducts +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(:Product {name: 'Chocolate'}) +RETURN c AS customer + +NEXT + +RETURN customer.firstName AS plantCustomer +} + +UNION ALL + +{ +MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Coffee'}) +RETURN c AS customer + +NEXT + +RETURN customer.firstName AS plantCustomer +} +---- + +.Result +[role="queryresult",options="header,footer",cols="1* "Coffee" +CALL (p) { + MATCH (p)<-[:BUYS]-(c:Customer)-[:BUYS]->(otherProduct) + RETURN c, otherProduct + + NEXT + + RETURN count(DISTINCT c) AS customers, 0 AS customersAlsoBuyingCoffee + UNION + FILTER otherProduct.name = "Coffee" + RETURN 0 as customers, count(DISTINCT c) AS customersAlsoBuyingCoffee + + NEXT + + RETURN max(customers) AS customers, max(customersAlsoBuyingCoffee) AS customersAlsoBuyingCoffee +} +RETURN p.name AS product, + round(toFloat(customersAlsoBuyingCoffee) * 100 / customers, 1) AS percentageOfCustomersAlsoBuyingCoffee + ORDER BY product ---- -// end::sequential_queries_call[] -====== -==== .Result [role="queryresult",options="header,footer",cols="2*(:Product)<-[:SUPPLIES]-(s:Supplier) RETURN c.firstName AS customer, s.name AS supplier @@ -256,14 +430,13 @@ The second segment is a conditional query that returns different base personalit The third segment aggregates the personality types. Finally, the fourth segment is another conditional query which subsumes multiple base personality types, if present, to a new personality. -[[next-conditional-queries-top-level-braces]] +[[next-inside-conditional-queries]] === `NEXT` inside a conditional query using `{}` If a conditional query has a `NEXT` in any of its `THEN` or `ELSE` blocks, it is necessary to wrap the part after `THEN` or `ELSE` with `{}`. -.`NEXT` inside a conditional query -// tag::sequential_queries_in_conditional_queries[] -[source, cypher] +.`NEXT` inside conditional query +[source,cypher] ---- MATCH (c:Customer)-[:BUYS]->(p:Product) RETURN c AS customer, sum(p.price) AS sum @@ -281,7 +454,6 @@ ELSE { RETURN customer.firstName AS customer, "club below 1000" AS customerType, finalSum AS sum } ---- -// end::sequential_queries_in_conditional_queries[] .Result [role="queryresult",options="header,footer",cols="3*(:Product{name: "Laptop"}) -RETURN c.firstName AS customer -UNION ALL -MATCH (c:Customer)-[:BUYS]-> (:Product{name: "Coffee"}) -RETURN c.firstName AS customer +[[issues-fixes]] +== Known issues and fixes -NEXT - -RETURN customer AS customer, count(customer) as numberOfProducts ----- +`NEXT` initially did not correctly handle aggregations in the context of `UNION` branches and `CALL` subqueries. +As of Neo4j 2025.08, this issue has been fixed. +The table below summarizes the behavior changes across versions. -.Result -[role="queryresult",options="header,footer",cols="2*(:Product {name: 'Chocolate'}) -RETURN c AS customer - -NEXT - -RETURN customer.firstName AS plantCustomer -} - -UNION ALL - -{ -MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Coffee'}) -RETURN c AS customer - -NEXT - -RETURN customer.firstName AS plantCustomer -} ----- - -.Result -[role="queryresult",options="header,footer",cols="1*