diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 1f2ddf6d0..aca26e744 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -9,6 +9,7 @@ ** xref:queries/composed-queries/index.adoc[] *** xref:queries/composed-queries/combined-queries.adoc[] *** xref:queries/composed-queries/conditional-queries.adoc[] +*** xref:queries/composed-queries/sequential-queries.adoc[] * xref:clauses/index.adoc[] ** xref:clauses/clause-composition.adoc[] diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc index 0f930c4ab..fb81ed00a 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -46,7 +46,8 @@ These codes order the features in the table below. | G020 | Counted shortest group search | xref:patterns/shortest-paths.adoc#shortest-groups[`SHORTEST GROUPS`] -| +| + | G035 | Quantified paths @@ -188,6 +189,11 @@ For example, GQL’s graph reference values `CURRENT_GRAPH` and `CURRENT_PROPERT | xref:clauses/limit.adoc[`LIMIT`], xref:clauses/order-by.adoc[`ORDER BY`] | Cypher requires using the xref:clauses/with.adoc[`WITH`] clause, which GQL does not. +| GQ20 +| Advanced linear composition with NEXT +| xref:queries/composed-queries/sequential-queries.adoc[] +| The GQL standard includes a `YIELD` clause for its `NEXT` statement which Cypher does not implement. + | GV39 | Temporal types: date, local datetime, and local time support | xref:values-and-types/temporal.adoc[Temporal types], xref:functions/temporal/index.adoc#functions-date[`date()`] diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 9be462803..337126eef 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -224,6 +224,22 @@ a| label:functionality[] label:new[] +[source, cypher, role="noheader"] +---- +RETURN 1 AS a + +NEXT + +RETURN 1 AS b +---- + +| New `NEXT` keyword used for linear composition of queries. +For more information, see xref:queries/composed-queries/sequential-queries.adoc[]. + +a| +label:functionality[] +label:new[] + [source, cypher, role="noheader"] ---- MATCH (s:Supplier)-[:SUPPLIES]->(p:Product) diff --git a/modules/ROOT/pages/queries/composed-queries/index.adoc b/modules/ROOT/pages/queries/composed-queries/index.adoc index 297a2a78c..fc74dbb97 100644 --- a/modules/ROOT/pages/queries/composed-queries/index.adoc +++ b/modules/ROOT/pages/queries/composed-queries/index.adoc @@ -1,11 +1,14 @@ = Composed queries :description: Overview about how to use `UNION` and `WHEN` to construct combined or conditional queries in Cypher. -`UNION` and `WHEN` enable the composition of multiple separate query branches within a single query. -`UNION` allows for combining the results of different queries, while `WHEN` enables conditional queries, where different query branches can be made to execute depending on a set of criteria. -As such, `UNION` and `WHEN` manage in different ways the execution flow and logic of queries and cannot be used in queries as regular clauses. +`UNION`, `WHEN` and `NEXT` enable the composition of multiple separate query branches within a single query. +`UNION` allows for combining the results of different queries. +`WHEN` enables conditional queries, where different query branches can be made to execute depending on a set of criteria. +Finally, `NEXT` allows for the linear composition of sequential queries, passing the return values from one query to the next. +As such, `UNION`, `WHEN`, and `NEXT` manage in different ways the execution flow and logic of queries and cannot be used in queries as regular clauses. For more information, see: * xref:queries/composed-queries/combined-queries.adoc[] * xref:queries/composed-queries/conditional-queries.adoc[] label:new[Introduced in Neo4j 2025.06] +* xref:queries/composed-queries/sequential-queries.adoc[] label:new[Introduced in Neo4j 2025.06] diff --git a/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc b/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc new file mode 100644 index 000000000..29c84b486 --- /dev/null +++ b/modules/ROOT/pages/queries/composed-queries/sequential-queries.adoc @@ -0,0 +1,375 @@ += Sequential queries (`NEXT`) +:description: Information about how to use `NEXT` to construct sequential queries in Cypher. +: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` 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. + +[[example-graph]] +== Example graph + +The following graph is used for the examples on this page: + +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) +---- + +[[syntax]] +== Syntax + +.`NEXT` syntax +[source, cypher] +---- + + +NEXT + + + +NEXT + + +---- + +[[passing-values]] +== Passing values to subsequent queries + +In the following example, `NEXT` passes the variable `customer` to the second query: + +.Passing a variable to another query via `NEXT` +[source, cypher] +---- +MATCH (c:Customer) +RETURN c AS customer + +NEXT + +MATCH (customer)-[:BUYS]->(:Product {name: 'Chocolate'}) +RETURN customer.firstName AS chocolateCustomer +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(p:Product {name: 'Chocolate'}) +RETURN c AS customer, p AS product + +NEXT + +RETURN customer.firstName AS chocolateCustomer, + product.price * (1 - customer.discount) AS chocolatePrice +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(p) + RETURN collect(c.firstName) AS customers +} +RETURN p.name as product, customers +---- +====== +[.include-with-NEXT] +====== +[source, cypher] +---- +MATCH (p:Product) +RETURN p + +NEXT + +MATCH (c:Customer)-[:BUYS]->(p) +RETURN collect(c.firstName) AS customers, p + +NEXT + +RETURN p.name as product, customers +---- +====== +==== + +.Result +[role="queryresult",options="header,footer",cols="2*(:Product)<-[:SUPPLIES]-(s:Supplier) +RETURN c.firstName AS customer, s.name AS supplier + +NEXT + +WHEN supplier = "TechCorp" THEN + RETURN customer, "Tech enjoyer" AS personality +WHEN supplier = "Foodies Inc." THEN + RETURN customer, "Tropical plant enjoyer" AS personality + +NEXT + +RETURN customer, collect(DISTINCT personality) AS personalities + +NEXT + +WHEN size(personalities) > 1 THEN + RETURN customer, "Enjoyer of tech and plants" AS personality +ELSE + RETURN customer, personalities[0] AS personality +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(p:Product) +RETURN c AS customer, sum(p.price) AS sum + +NEXT + +WHEN sum >= 1000 THEN { + RETURN customer.firstName AS customer, "club 1000 plus" AS customerType, sum AS sum +} +ELSE { + RETURN customer AS customer, sum * (1 - customer.discount) AS finalSum + + NEXT + + RETURN customer.firstName AS customer, "club below 1000" AS customerType, finalSum AS sum +} +---- + +.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*