diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index d6ea0bd0d..2d08c7ecc 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -6,8 +6,9 @@ * xref:queries/index.adoc[] ** xref:queries/concepts.adoc[] ** xref:queries/basic.adoc[] -** xref:queries/expressions.adoc[] -** xref:queries/case.adoc[] +** xref:queries/composed-queries/index.adoc[] +*** xref:queries/composed-queries/combined-queries.adoc[] +*** xref:queries/composed-queries/conditional-queries.adoc[] * xref:clauses/index.adoc[] ** xref:clauses/clause-composition.adoc[] @@ -31,12 +32,15 @@ ** xref:clauses/transaction-clauses.adoc#query-listing-transactions[SHOW TRANSACTIONS] ** xref:clauses/skip.adoc[] ** xref:clauses/transaction-clauses.adoc#query-terminate-transactions[TERMINATE TRANSACTIONS] -** xref:clauses/union.adoc[] ** xref:clauses/unwind.adoc[] ** xref:clauses/use.adoc[] ** xref:clauses/where.adoc[] ** xref:clauses/with.adoc[] +* xref:expressions/index.adoc[] +** xref:expressions/expressions-overview.adoc[] +** xref:expressions/conditional-expressions.adoc[] + * xref:subqueries/index.adoc[] ** xref:subqueries/call-subquery.adoc[] ** xref:subqueries/subqueries-in-transactions.adoc[] diff --git a/modules/ROOT/images/conditional_query_graph.svg b/modules/ROOT/images/conditional_query_graph.svg new file mode 100644 index 000000000..4e3d7b1a0 --- /dev/null +++ b/modules/ROOT/images/conditional_query_graph.svg @@ -0,0 +1 @@ +WORKS_FORWORKS_FORWORKS_FORLOVESLOVESPersonname:'Alice'age:65Personname:'Bob'age:25Personname:'Charlie'age:61Personname:'Daniel'age:39Personname:'Eskil'age:39 \ No newline at end of file diff --git a/modules/ROOT/pages/appendix/gql-conformance/index.adoc b/modules/ROOT/pages/appendix/gql-conformance/index.adoc index 438f89042..2a09dc445 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/index.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/index.adoc @@ -1,8 +1,8 @@ :description: Overview of Cypher's conformance to GQL. = GQL conformance -*Last updated*: 24 October 2024 + -*Neo4j version*: 5.25 +*Last updated*: 21 February 2025 + +*Neo4j version*: 2025.03 GQL is the new link:https://www.iso.org/home.html[ISO] International Standard query language for graph databases. diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index 4551dd8f6..3504f299e 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -28,6 +28,11 @@ The below table is instead listed in order of their appearance in the link:https Cypher supports the boolean type predicate for `TRUE`, `FALSE`, and `NULL` but does not support the GQL keyword `UNKNOWN`. +| 9.1 +| +| xref:queries/composed-queries/combined-queries.adoc#combining-union-and-union-all[Combining `UNION` and `UNION ALL`] +| + | 13.2 | | xref:clauses/create.adoc#insert-as-synonym-of-create[`INSERT`] @@ -71,6 +76,11 @@ The only way to guarantee row order in Neo4j is to use xref:clauses/order-by.ado | GQL defines the option to specify `RETURN ALL` (functionally equivalent to using `RETURN` on its own). This is currently not available in Cypher. +| 15.4 +| +| xref:queries/composed-queries/conditional-queries.adoc[] +| + | 16.2 | | xref:clauses/limit.adoc[`LIMIT`] @@ -148,7 +158,7 @@ This is currently not available in Cypher. | 20.2 | -| xref:queries/expressions.adoc[] +| xref:expressions/expressions-overview.adoc[] | | 20.3 @@ -159,7 +169,7 @@ In Cypher, current user details can be seen using the link:{neo4j-docs-base-uri} | 20.7 | -| xref:queries/case.adoc[`CASE`], xref:functions/scalar.adoc#functions-nullIf[`nullIf()`], xref:functions/scalar.adoc#functions-coalesce[`coalesce()`] +| xref:expressions/conditional-expressions.adoc[`CASE`], xref:functions/scalar.adoc#functions-nullIf[`nullIf()`], xref:functions/scalar.adoc#functions-coalesce[`coalesce()`] | | 20.9 diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc index 2e8c0778d..64d09b71c 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -170,7 +170,7 @@ For example, GQL’s graph reference values `CURRENT_GRAPH` and `CURRENT_PROPERT | GQ03 | Composite query: `UNION` -| xref:clauses/union.adoc[`UNION`] +| xref:queries/composed-queries/combined-queries.adoc[`UNION`] | | GQ13 diff --git a/modules/ROOT/pages/clauses/clause-composition.adoc b/modules/ROOT/pages/clauses/clause-composition.adoc index 416906fcf..d3e1949d0 100644 --- a/modules/ROOT/pages/clauses/clause-composition.adoc +++ b/modules/ROOT/pages/clauses/clause-composition.adoc @@ -297,7 +297,7 @@ the graph made by the `CREATE`. [[cypher-clause-composition-union-queries]] == Queries with `UNION` -xref::clauses/union.adoc[`UNION`] queries are slightly different because the results of two or more queries are put together, +xref::queries/composed-queries/combined-queries.adoc[`UNION`] queries are slightly different because the results of two or more queries are put together, but each query starts with an empty table of intermediate results. In a query with a `UNION` clause, any clause _before_ the `UNION` cannot observe writes made by a clause _after_ the `UNION`. diff --git a/modules/ROOT/pages/clauses/index.adoc b/modules/ROOT/pages/clauses/index.adoc index 89e88c90b..659bfa3fc 100644 --- a/modules/ROOT/pages/clauses/index.adoc +++ b/modules/ROOT/pages/clauses/index.adoc @@ -152,12 +152,12 @@ Typically used when modifying or importing large amounts of data. |=== |Clause |Description -m| xref::clauses/union.adoc[UNION] +m| xref::queries/composed-queries/combined-queries.adoc[UNION] a| Combines the result of multiple queries into a single result set. Duplicates are removed. -m| xref::clauses/union.adoc[UNION ALL] +m| xref::queries/composed-queries/combined-queries.adoc[UNION ALL] a| Combines the result of multiple queries into a single result set. Duplicates are retained. diff --git a/modules/ROOT/pages/clauses/use.adoc b/modules/ROOT/pages/clauses/use.adoc index 70beae342..cb6552015 100644 --- a/modules/ROOT/pages/clauses/use.adoc +++ b/modules/ROOT/pages/clauses/use.adoc @@ -25,7 +25,7 @@ When connected to a composite database, a graph reference may additionally be pa * The graph function xref:functions/graph.adoc#functions-graph-byname[`graph.byName()`], which allows the graph reference to be resolved dynamically: `USE graph.byName()`. -A more detailed description of how and when a graph references needs to be quoted and/or escaped is defined xref::queries/expressions.adoc#graphreferences[here]. +A more detailed description of how and when a graph references needs to be quoted and/or escaped is defined xref::expressions/expressions-overview.adoc#graphreferences[here]. == USE clause when connected to a standard or system database diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index c1253b352..87abb66bb 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -461,7 +461,7 @@ The `name`, `age`, and `email` values for `Peter` are returned because his email Note that the regular expression constructs in link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html[Java regular expressions] are applied only after resolving the escaped character sequences in the given -xref::queries/expressions#expressions-string-literals[string literal]. +xref::expressions/expressions-overview.adoc#expressions-string-literals[string literal]. It is sometimes necessary to add additional backslashes to express regular expression constructs. This list clarifies the combination of these two definitions, containing the original escape sequence and the resulting character in the regular expression: @@ -532,7 +532,7 @@ In other words, it must contain at least one xref::patterns/reference.adoc#relat * Path pattern expressions may not declare new variables. They can only reference existing variables. -* Path pattern expressions may only be used in positions where a xref:queries/expressions.adoc#boolean[boolean expression] is expected. +* Path pattern expressions may only be used in positions where a xref:expressions/expressions-overview.adoc#boolean[boolean expression] is expected. The following sections will demonstrate how to use path pattern expressions in a `WHERE` clause. [[filter-on-patterns]] diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 643780166..e0791df44 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -162,8 +162,8 @@ USE graph.byName('tom`s-database') USE graph.propertiesByName('database.with.dot') ---- -| xref::queries/expressions.adoc#graphreferences[Graph references] in arguments of the functions xref:functions/graph.adoc#functions-graph-byname[`graph.byName`] and xref:functions/graph.adoc#functions-graph-propertiesByName[`graph.propertiesByName`] in Cypher 25 are parsed as `` or `.` and now support escaping names. -For more information, see xref:syntax/expressions.adoc#graph-references[Cypher expressions -> Graph references. +| xref::expressions/expressions-overview.adoc#graphreferences[Graph references] in arguments of the functions xref:functions/graph.adoc#functions-graph-byname[`graph.byName`] and xref:functions/graph.adoc#functions-graph-propertiesByName[`graph.propertiesByName`] in Cypher 25 are parsed as `` or `.` and now support escaping names. +For more information, see xref:syntax/expressions.adoc#graph-references[Cypher expressions -> Graph references]. Graph name parts that contain unsupported characters for unescaped symbolic names now require backtick quoting. Graph name parts with special characters may require additional escaping of those characters: @@ -193,6 +193,56 @@ RETURN count(p) AS count |=== | Feature | Details + +a| +label:functionality[] +label:new[] + +[source, cypher, role="noheader"] +---- +WHEN false THEN RETURN 1 AS x +WHEN true THEN RETURN 2 AS x +ELSE RETURN 3 AS x +---- + +[source, cypher, role="noheader"] +---- + MATCH (n:Person) + OPTIONAL MATCH (n)-[:KNOWS]->(m) + CALL (*) { + WHEN m IS NULL THEN { + CREATE (f: Person {name: 'Peter', age: n.age}), + (n)-[:KNOWS]->(f) + RETURN f, n.name AS newConnection + } + } +RETURN f.name AS newNode, + collect(newConnection) AS newConnections +---- + +| Introduction of `WHEN`/`ELSE` branches which enable the composition of conditional queries, similar to the `IF` statement in other programming languages. +For more information, see xref:queries/composed-queries/conditional-queries.adoc[]. + +a| +label:functionality[] +label:new[] +[source, cypher, role="noheader"] +---- +{ + MATCH (n:Actor) + RETURN n.name AS name + UNION + MATCH (n:Director) + RETURN n.name AS name +} +UNION ALL +MATCH (n:Movie) +RETURN n.title AS name +---- + +| `UNION [DISTINCT]` and `UNION ALL` can now be combined in the same query by using curly braces. +For more information, see xref:queries/composed-queries/combined-queries.adoc#combining-union-and-union-all[Combining `UNION` and `UNION ALL`]. + a| label:functionality[] label:new[] @@ -296,7 +346,7 @@ label:deprecated[] ---- CASE x ... WHEN is :: STRING THEN ... END ---- -a| Using a variable named `is` (or any casing variant, like `IS`) as a `WHEN` operand in a xref:queries/case.adoc#case-simple[simple `CASE`] expression is deprecated. +a| Using a variable named `is` (or any casing variant, like `IS`) as a `WHEN` operand in a xref:expressions/conditional-expressions.adoc#case-simple[simple `CASE`] expression is deprecated. To continue using variables with this name in simple `CASE` expressions, use backticks to quote the variable name: `CASE x ... WHEN ++`is`++ :: STRING THEN ... END` a| @@ -309,7 +359,7 @@ CASE x ... WHEN contains + 1 THEN ... END ---- CASE x ... WHEN contains - 1 THEN ... END ---- -a| Using a variable named `contains` (or any casing variant, like `CONTAINS`) in addition or subtraction operations within a `WHEN` operand of a xref:queries/case.adoc#case-simple[simple `CASE`] expression is deprecated. +a| Using a variable named `contains` (or any casing variant, like `CONTAINS`) in addition or subtraction operations within a `WHEN` operand of a xref:expressions/conditional-expressions.adoc#case-simple[simple `CASE`] expression is deprecated. To continue using variables with this name, use backticks to quote the variable name: * Additions: `CASE x ... WHEN ++`contains`++ + 1 THEN ... END` @@ -325,7 +375,7 @@ CASE x ... WHEN in[1] THEN ... END ---- CASE x ... WHEN in["abc"] THEN ... END ---- -a| Using the `[]` operator on a variable named `in` (or any casing variant, like `IN`) within a `WHEN` operand of a xref:queries/case.adoc#case-simple[simple `CASE`] expression is deprecated. +a| Using the `[]` operator on a variable named `in` (or any casing variant, like `IN`) within a `WHEN` operand of a xref:expressions/conditional-expressions.adoc#case-simple[simple `CASE`] expression is deprecated. To continue using variables with this name, use backticks to quote the variable name: * `CASE x ... WHEN ++`in`++[1] THEN ... END` @@ -1017,7 +1067,7 @@ RETURN 1 AS a UNION DISTINCT RETURN 1 AS a ---- -| The keyword `DISTINCT` can now be added after a xref:clauses/union.adoc#union-distinct[UNION] as the explicit form of a `UNION` with duplicate removal. +| The keyword `DISTINCT` can now be added after a xref:queries/composed-queries/combined-queries.adoc#union-distinct[UNION] as the explicit form of a `UNION` with duplicate removal. a| label:functionality[] @@ -1076,9 +1126,9 @@ RETURN CASE n.prop END ---- -| Extension of the xref::queries/case.adoc#case-simple[simple `CASE` expression], allowing multiple matching values to be comma-separated in the same `WHEN` statement. +| Extension of the xref::expressions/conditional-expressions.adoc#case-simple[simple `CASE` expression], allowing multiple matching values to be comma-separated in the same `WHEN` statement. The simple `CASE` uses an implied equals (`=`) comparator, and this extension additionally allows other comparison predicates to be explicitly specified before the matching value -in an xref::queries/case.adoc#case-extended-simple[extended version of the simple `CASE`]. +in an xref::expressions/conditional-expressions.adoc#case-extended-simple[extended version of the simple `CASE`]. a| label:functionality[] diff --git a/modules/ROOT/pages/queries/case.adoc b/modules/ROOT/pages/expressions/conditional-expressions.adoc similarity index 99% rename from modules/ROOT/pages/queries/case.adoc rename to modules/ROOT/pages/expressions/conditional-expressions.adoc index be2a3420d..9c6d93c21 100644 --- a/modules/ROOT/pages/queries/case.adoc +++ b/modules/ROOT/pages/expressions/conditional-expressions.adoc @@ -15,7 +15,7 @@ Two variants of `CASE` exist within Cypher: the _simple_ form, to compare a sing The following graph is used for the examples below: -image:case_graph.svg[width="500",role="middle"] +image::case_graph.svg[width="400",role="middle"] To recreate the graph, run the following query against an empty Neo4j database: diff --git a/modules/ROOT/pages/queries/expressions.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc similarity index 91% rename from modules/ROOT/pages/queries/expressions.adoc rename to modules/ROOT/pages/expressions/expressions-overview.adoc index f4ba6f036..e45863e9f 100644 --- a/modules/ROOT/pages/queries/expressions.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -1,7 +1,7 @@ -= Cypher expressions -:description: This page explains which expressions are allowed in Cypher. += Overview +:description: Overview of the expressions allowed in Cypher. -This page contains examples of allowed expressions in Cypher. +This page contains an overview of the allowed expressions in Cypher. [[general]] == General @@ -100,7 +100,7 @@ To refer to a database with a dot (`.`) in its name, quote the graph reference i * When resolving a graph reference within a graph function, the string argument is parsed like a static graph reference. Thus, `USE graph.byName()` is typically equivalent to `USE `. However, escaping rules for xref::syntax/naming.adoc#symbolic-names-escaping-rules[symbolic names] are applied to the argument. - For string literals, both the escaping rules for xref:queries/expressions.adoc#expressions-string-literals[string literals] (during query parsing) and xref::syntax/naming.adoc#symbolic-names-escaping-rules[symbolic names] (during graph reference evaluation) are applied. + For string literals, both the escaping rules for xref:expressions/expressions-overview.adoc#expressions-string-literals[string literals] (during query parsing) and xref::syntax/naming.adoc#symbolic-names-escaping-rules[symbolic names] (during graph reference evaluation) are applied. For example, the graph reference in `USE graph.byName('+composite.1\\u0041+')` resolves to the constituent `composite.1a` of the composite database `composite`. diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc new file mode 100644 index 000000000..144420147 --- /dev/null +++ b/modules/ROOT/pages/expressions/index.adoc @@ -0,0 +1,9 @@ += Expressions + +In Cypher, an expression is any combination of components that evaluates to a result. +Expressions are used to perform operations like calculations, comparisons, and data transformations within a query. + +This section includes: + +* xref:expressions/expressions-overview.adoc[] +* xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/patterns/reference.adoc b/modules/ROOT/pages/patterns/reference.adoc index c1f61415c..c469e8529 100644 --- a/modules/ROOT/pages/patterns/reference.adoc +++ b/modules/ROOT/pages/patterns/reference.adoc @@ -344,7 +344,7 @@ is equivalent to the following node pattern with a `WHERE` clause: (n WHERE n.p = valueExp1 AND n.q = valueExp2) ---- -The value expression can be any expression as listed in the section on xref:queries/expressions.adoc[expressions], except for path patterns (which will throw a syntax error) and regular expressions (which will be treated as string literals). +The value expression can be any expression as listed in the section on xref:expressions/expressions-overview.adoc[expressions], except for path patterns (which will throw a syntax error) and regular expressions (which will be treated as string literals). An empty property key-value expression matches all elements. Property key-value expressions can be combined with a `WHERE` clause. diff --git a/modules/ROOT/pages/patterns/variable-length-patterns.adoc b/modules/ROOT/pages/patterns/variable-length-patterns.adoc index afdaf8b7e..7794f8914 100644 --- a/modules/ROOT/pages/patterns/variable-length-patterns.adoc +++ b/modules/ROOT/pages/patterns/variable-length-patterns.adoc @@ -77,7 +77,7 @@ Translating the motifs into Cypher, and adding predicates to match the origin an (:Station { name: 'Clapham Junction' }) ---- -To return both solutions in the same query using these fixed-length path patterns, a xref:clauses/union.adoc[UNION] of two `MATCH` statements would be needed. +To return both solutions in the same query using these fixed-length path patterns, a xref:queries/composed-queries/combined-queries.adoc[UNION] of two `MATCH` statements would be needed. For example, the following query returns the `departure` of the two services: .Query diff --git a/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc b/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc index 5e079873e..3b43868b2 100644 --- a/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc +++ b/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc @@ -4535,7 +4535,7 @@ Total database accesses: 256, total allocated memory: 7376 == Union operators Union operators in Cypher combine the results from multiple query parts by merging their rows. -For more information, see the page about the xref:clauses/union.adoc[`UNION`] clause. +For more information, see the page about the xref:queries/composed-queries/combined-queries.adoc[`UNION`] clause. [[query-plan-union]] diff --git a/modules/ROOT/pages/clauses/union.adoc b/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc similarity index 82% rename from modules/ROOT/pages/clauses/union.adoc rename to modules/ROOT/pages/queries/composed-queries/combined-queries.adoc index b193a7786..d104924d9 100644 --- a/modules/ROOT/pages/clauses/union.adoc +++ b/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc @@ -1,7 +1,7 @@ :description: The `UNION` clause is used to combine the result of multiple queries. [[query-union]] -= UNION += Combined queries (`UNION`) `UNION` combines the results of two or more queries into a single result set that includes all the rows that belong to any queries in the union. @@ -155,4 +155,39 @@ ORDER BY count 2+d|Rows: 3 |=== -For more information, see xref:subqueries/call-subquery.adoc#call-post-union[`CALL` subqueries -> Post-union processing]. \ No newline at end of file +For more information, see xref:subqueries/call-subquery.adoc#call-post-union[`CALL` subqueries -> Post-union processing]. + +[role=label--new-2025.03] +[[combining-union-and-union-all]] +== Combining UNION and UNION ALL + +To combine `UNION` (or `UNION DISTINCT`) and `UNION ALL` in the same query, enclose one or more `UNION` operations of the same type in curly braces. +This allows the enclosed query to act as an argument that can be combined with an outer `UNION` operation of any type. + +.Combine `UNION` and `UNION ALL` +[source, cypher] +---- +{ + MATCH (n:Actor) + RETURN n.name AS name + UNION + MATCH (n:Director) + RETURN n.name AS name +} +UNION ALL +MATCH (n:Movie) +RETURN n.title AS name +---- + +The combined result is returned. + +.Result +[role="queryresult",options="header,footer",cols="1*(alice), + (alice)-[:WORKS_FOR]->(daniel), + (charlie)-[:WORKS_FOR]->(daniel), + (bob)-[:LOVES]->(eskil), + (charlie)-[:LOVES]->(alice) +---- + +[[standalone-when-logic]] +== Standalone `WHEN` branches + +.Syntax for standalone `WHEN` branches +[source, syntax] +---- +WHEN predicate THEN [{] + + [}] +[WHEN ...]* +[ELSE [{] + +[}]] +---- + +The first branch with a predicate that evaluates to `true` will be executed. +If no `WHEN` branches are executed and an `ELSE` branch exists, it is executed. +If no `WHEN` branches evaluates to `true` and no `ELSE` branch is present, no branches are executed and no rows are produced. +The following examples demonstrates this logic: + +.Conditional logic +[source, cypher] +---- +WHEN false THEN RETURN 1 AS x +WHEN true THEN RETURN 2 AS x +WHEN true THEN RETURN 3 AS x +ELSE RETURN 3 AS x +---- + +Since the second `WHEN` branch is `true`, it will execute, while the preceding branch (which is `false`) and the succeeding `WHEN` branch (which is `true`) as well as the `ELSE` branch will be skipped. + +.Result +[role="queryresult",options="header,footer",cols="1*(m:Person) + WHEN m IS NULL THEN { + MERGE (n)-[:WORKS_FOR]->(f: Person {name: 'Peter', age: 36}) + } +RETURN n.name AS employees, + m.name AS manager, + f.name AS newManagerNode +---- + +Instead, if `WHEN` constructs are part of a larger query, they must either be placed within a subquery and/or on different sides of combined `UNION` queries. + +[[conditional-subqueries]] +== Conditional subqueries + +`WHEN` can be used inside one or several xref:subqueries/call-subquery.adoc[`CALL` subqueries] to execute a set of operations only when a specified condition evaluates to `true`. + +.Syntax for conditional `CALL` subqueries +[source, syntax] +---- +[] + { + WHEN predicate THEN [{] + + [}] + [WHEN ...]* + [ELSE [{] + + [}]] +} +[ ...]* +[] +---- + +.Single conditional `CALL` subquery +===== +In this example, `WHEN` is used to execute a xref:subqueries/call-subquery.adoc[`CALL` subquery] for each row that the condition (`m IS NULL`) evaluates to `true`. + +.Conditional `CALL` subquery +[source, cypher] +---- +MATCH (n:Person) +OPTIONAL MATCH (n)-[:WORKS_FOR]->(m:Person) +CALL (*) { + WHEN m IS NULL THEN { + MERGE (f: Person {name: 'Peter', age: 36}) + MERGE (n)-[:WORKS_FOR]->(f) + RETURN f, n.name AS employee + } +} +RETURN f.name AS manager, + collect(employee) AS employees +---- + +Because only `Daniel` and `Eskil` had no outgoing `WORKS_FOR` relationships, they have now been connected as employees of the new `Peter` node. + +.Result +[role="queryresult",options="header,footer",cols="2*(m:Person) +CALL (*) { + WHEN n.age > 60 THEN { + SET n.ageGroup = 'Veteran' + RETURN n.ageGroup AS ageGroup + } + WHEN n.age >= 35 AND n.age <= 59 THEN { + SET n.ageGroup = 'Senior' + RETURN n.ageGroup AS ageGroup + } + ELSE { + SET n.ageGroup = 'Junior' + RETURN n.ageGroup AS ageGroup + } +} +CALL (*) { + WHEN m.age > n.age THEN { + RETURN collect([m.name, m.ageGroup]) AS manager + } +} +RETURN n.name AS name, ageGroup, manager +---- + +`Bob` is returned because he is the only person in the graph with an older manager. + +.Result +[role="queryresult",options="header,footer",cols="3*] +EXISTS|COUNT|COLLECT { + WHEN predicate THEN [{] + + [}] + [WHEN ...]* + [ELSE [{] + + [}]] +} +[] +---- + + +.Conditional `EXISTS` subquery +===== +In this example, `WHEN` is used inside an `EXISTS` subquery to conditionally execute different branches based on the evaluation of the predicate (`n.age > 40`). + +[NOTE] +Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery are not available to the outer scope (the same is true for `COUNT` and `COLLECT` subqueries). + +.`WHEN` inside an `EXISTS` subquery +[source, cypher] +---- +MATCH (n:Person) +WHERE EXISTS { + WHEN n.age > 40 THEN { + RETURN n.name AS x + } + ELSE { + MATCH (n)-[:LOVES]->(x:Person) + RETURN x + } +} +RETURN n.name AS name, + n.age AS age +---- + +`Alice` and `Charlie` are both older than `40,` so they are returned by the `WHEN` branch, while `Bob` is returned by the `ELSE` branch. +Note that some `Person` nodes in the graph are not matched in either branch of the conditional subquery, and are therefore not returned. + +.Result +[role="queryresult",options="header,footer",cols="2* + [}] + [WHEN ...]* + [ELSE [{] + +[}]] +} +UNION [DISTINCT|ALL] +{ + WHEN predicate THEN [{] + + [}] + [WHEN ...]* + [ELSE [{] + +[}]] +} +[UNION [DISTINCT|ALL] ...]* +---- + +.Combining conditional branches with `UNION` using `{}` +[source, cypher] +---- +{ + WHEN true THEN RETURN 1 AS x + WHEN false THEN RETURN 2 AS x + ELSE RETURN 3 AS x +} +UNION +{ + WHEN false THEN RETURN 4 AS x + WHEN false THEN RETURN 5 AS x + ELSE RETURN 6 AS x +} +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(m:Person) + CALL (*) { + WHEN r IS NULL THEN { + RETURN n.name AS person, "Loves no one" AS message + } + ELSE { + RETURN n.name AS person, "Loves somebody" AS message + } + } + RETURN person, message + UNION + CALL (*) { + WHEN n.age < 40 THEN { + RETURN n.name AS person, "Under 40" AS message + } + ELSE { + RETURN n.name AS person, "40 or older" AS message + } + } + RETURN person, message +} +RETURN person, collect(message) AS status +---- + +.Result +[role="queryresult",options="header,footer",cols="2* Conditional subqueries]. + +.Conditional `CALL` subqueries +===== + +This example uses conditional logic to categorize players based on age by executing `WHEN`/`ELSE` branches, setting the `ageGroup` property as either `"Senior"` or `"Junior"` for each player depending on their age. + +.Categorize players by age using a conditional WHEN and ELSE for each team. +[source, cypher] +---- +MATCH (t:Team) +OPTIONAL MATCH (p:Player)-[:PLAYS_FOR]->(t) +CALL (*) { + WHEN p.age > 25 THEN { + SET p.ageGroup = "Senior" + RETURN p.name AS player, p.ageGroup AS ageGroup + } + ELSE { + SET p.ageGroup = "Junior" + RETURN p.name AS player, p.ageGroup AS ageGroup + } +} +RETURN player, ageGroup +---- + +.Result +[role="queryresult",options="header,footer",cols="2*m"] +|=== +| player | ageGroup + +| "Player A" | "Junior" +| "Player B" | "Junior" +| "Player D" | "Senior" +| "Player E" | "Junior" +| "Player F" | "Senior" + +2+d|Rows: 5 +|=== + +===== + + [[call-execution-order]] == Execution order of CALL subqueries @@ -563,7 +610,7 @@ RETURN [[call-post-union]] == Post-union processing -Call subqueries can be used to further process the results of a xref:clauses/union.adoc[] query. +Call subqueries can be used to further process the results of a xref:queries/composed-queries/combined-queries.adoc[] query. .Using `UNION` within a `CALL` subquery ==== diff --git a/modules/ROOT/pages/subqueries/collect.adoc b/modules/ROOT/pages/subqueries/collect.adoc index cd9825c1b..fb54399e7 100644 --- a/modules/ROOT/pages/subqueries/collect.adoc +++ b/modules/ROOT/pages/subqueries/collect.adoc @@ -74,6 +74,41 @@ RETURN person.name as name, COLLECT { 2+d|Rows: 3 |=== +[role=label--new-2025.03] +[[conditional-collect]] +== Conditional `COLLECT` subquery + +`WHEN` can be used inside `COLLECT` subqueries to execute branches conditionally when a predicate evaluates to `true`. +Note that the names and the number of columns returned by the different `WHEN` branches must be identical. +For more information, see xref:queries/composed-queries/conditional-queries.adoc#conditional-subqueries[Conditional queries -> Conditional subqueries]. + +In the example below query, the `WHEN` branch is executed if the person has a dog, while the `ELSE` branch runs for people without a dog. + +.Conditional `COLLECT` subquery +[source, cypher] +---- +MATCH (n:Person) +RETURN n.name AS name, + COLLECT { + WHEN exists((n)-[:HAS_DOG]->(:Dog)) THEN { + RETURN 'Dog owner' AS petStatus + } + ELSE { + RETURN 'Cat owner' AS petStatus + } + } AS petStatus +---- + +[role="queryresult",options="header,footer",cols="2* Conditional subqueries]. + +In the example below, the `WHEN` branch is executed if a person has no cat (`c IS NULL`), counting their dogs. +The `ELSE` branch runs for those who have a cat. + +.Conditional `COUNT` subquery +[source, cypher] +---- +MATCH (p:Person) +OPTIONAL MATCH (p)-[:HAS_CAT]->(c) +RETURN p.name AS person, c IS NOT NULL AS hasCat, + COUNT { + WHEN c IS NULL THEN { + MATCH (p)-[:HAS_DOG]->(dog) + RETURN dog.name AS petOwner + } + ELSE { + MATCH (p)-[:HAS_CAT]->(cat) + RETURN cat.name AS petOwner + } + } AS howManyPets +---- + +[role="queryresult",options="header,footer",cols="3* Conditional subqueries]. + +The example below filters people based on their age and pet ownership. +If someone is over 35, it checks for dog ownership, and if 35 or younger, it checks for cat ownership. +Note that `Peter`, being 35 and a dog owner, is excluded from the result. + +.Conditional `EXISTS` subquery +[source, cypher] +---- +MATCH (n:Person) +WHERE EXISTS { + WHEN n.age > 35 THEN { + MATCH (n)-[:HAS_DOG]->(:Dog) + RETURN n AS petOwner + } + ELSE { + MATCH (n)-[:HAS_CAT]->(:Cat) + RETURN n AS petOwner + } +} +RETURN n.name AS name, + n.age AS age +---- + +[role="queryresult",options="header,footer",cols="2*