From 605531c8a078ce804352ec6c6b88694c69930085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:33:45 +0100 Subject: [PATCH 01/32] initial --- modules/ROOT/content-nav.adoc | 5 ++++- .../gql-conformance/supported-mandatory.adoc | 2 +- ...ions-additions-removals-compatibility.adoc | 10 ++++----- .../{ => conditional-queries}/case.adoc | 0 .../conditional-clauses.adoc | 21 +++++++++++++++++++ .../queries/conditional-queries/index.adoc | 16 ++++++++++++++ modules/ROOT/pages/queries/index.adoc | 2 +- 7 files changed, 48 insertions(+), 8 deletions(-) rename modules/ROOT/pages/queries/{ => conditional-queries}/case.adoc (100%) create mode 100644 modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc create mode 100644 modules/ROOT/pages/queries/conditional-queries/index.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index d6ea0bd0d..5478ceee9 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -7,7 +7,10 @@ ** xref:queries/concepts.adoc[] ** xref:queries/basic.adoc[] ** xref:queries/expressions.adoc[] -** xref:queries/case.adoc[] +** xref:queries/conditional-queries/index.adoc[] +*** xref:queries/conditional-queries/case.adoc[] +*** xref:queries/conditional-queries/conditional-clauses.adoc[] + * xref:clauses/index.adoc[] ** xref:clauses/clause-composition.adoc[] diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index 4551dd8f6..c2839f942 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -159,7 +159,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:queries/conditional-queries/case.adoc[`CASE`], xref:functions/scalar.adoc#functions-nullIf[`nullIf()`], xref:functions/scalar.adoc#functions-coalesce[`coalesce()`] | | 20.9 diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 309887365..f35a13661 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -263,7 +263,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:queries/conditional-queries/case.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| @@ -276,7 +276,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:queries/conditional-queries/case.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` @@ -292,7 +292,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:queries/conditional-queries/case.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` @@ -1043,9 +1043,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::queries/conditional-queries/case.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::queries/conditional-queries/case.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/queries/conditional-queries/case.adoc similarity index 100% rename from modules/ROOT/pages/queries/case.adoc rename to modules/ROOT/pages/queries/conditional-queries/case.adoc diff --git a/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc b/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc new file mode 100644 index 000000000..3ed03b60d --- /dev/null +++ b/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc @@ -0,0 +1,21 @@ += Conditional query constructs (WHEN, THEN) +:page-role: new-2025.02 + +== Syntax + +== Standalone conditional queries + +.Example +---- +WHEN ... +WHEN ... +ELSE ... +// OK +---- + + +== Conditional subqueries + +== Conditional `UNION` queries + +== Rules \ No newline at end of file diff --git a/modules/ROOT/pages/queries/conditional-queries/index.adoc b/modules/ROOT/pages/queries/conditional-queries/index.adoc new file mode 100644 index 000000000..6862ccf8a --- /dev/null +++ b/modules/ROOT/pages/queries/conditional-queries/index.adoc @@ -0,0 +1,16 @@ += Conditional queries +:page-role: new-2025.02 + +== Syntax + +== Standalone conditional clauses + +=== Sequencing conditional clauses + +== Conditional clauses within subqueries + +== Conditional clauses and `UNION` clauses + +Top level braces + +== Rules diff --git a/modules/ROOT/pages/queries/index.adoc b/modules/ROOT/pages/queries/index.adoc index b5f941fee..ad19c5f55 100644 --- a/modules/ROOT/pages/queries/index.adoc +++ b/modules/ROOT/pages/queries/index.adoc @@ -7,4 +7,4 @@ It also contains information about Cypher expressions. * xref:queries/concepts.adoc[] * xref:queries/basic.adoc[] * xref:queries/expressions.adoc[] -* xref:queries/case.adoc[] +* xref:queries/conditional-queries/index.adoc[] From 976a78199ec4c4cc0fc69a93a9fcb016f2a0a904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:34:53 +0100 Subject: [PATCH 02/32] initial --- modules/ROOT/content-nav.adoc | 6 +- ...{case_graph.svg => conditionals_graph.svg} | 0 .../gql-conformance/supported-mandatory.adoc | 2 +- ...ions-additions-removals-compatibility.adoc | 10 +- .../conditional-clauses.adoc | 21 ---- .../queries/conditional-queries/index.adoc | 16 --- .../conditional-expressions.adoc} | 2 +- .../conditional-queries.adoc | 108 ++++++++++++++++++ .../conditional-query-constructs/index.adoc | 9 ++ modules/ROOT/pages/queries/index.adoc | 2 +- 10 files changed, 128 insertions(+), 48 deletions(-) rename modules/ROOT/images/{case_graph.svg => conditionals_graph.svg} (100%) delete mode 100644 modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc delete mode 100644 modules/ROOT/pages/queries/conditional-queries/index.adoc rename modules/ROOT/pages/queries/{conditional-queries/case.adoc => conditional-query-constructs/conditional-expressions.adoc} (99%) create mode 100644 modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc create mode 100644 modules/ROOT/pages/queries/conditional-query-constructs/index.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 5478ceee9..2210489ce 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -7,9 +7,9 @@ ** xref:queries/concepts.adoc[] ** xref:queries/basic.adoc[] ** xref:queries/expressions.adoc[] -** xref:queries/conditional-queries/index.adoc[] -*** xref:queries/conditional-queries/case.adoc[] -*** xref:queries/conditional-queries/conditional-clauses.adoc[] +** xref:queries/conditional-query-constructs/index.adoc[] +*** xref:queries/conditional-query-constructs/conditional-expressions.adoc[] +*** xref:queries/conditional-query-constructs/conditional-queries.adoc[] * xref:clauses/index.adoc[] diff --git a/modules/ROOT/images/case_graph.svg b/modules/ROOT/images/conditionals_graph.svg similarity index 100% rename from modules/ROOT/images/case_graph.svg rename to modules/ROOT/images/conditionals_graph.svg diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index c2839f942..f444b2d7c 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -159,7 +159,7 @@ In Cypher, current user details can be seen using the link:{neo4j-docs-base-uri} | 20.7 | -| xref:queries/conditional-queries/case.adoc[`CASE`], xref:functions/scalar.adoc#functions-nullIf[`nullIf()`], xref:functions/scalar.adoc#functions-coalesce[`coalesce()`] +| xref:queries/conditional-query-constructs/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/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index f35a13661..7deeb93d6 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -263,7 +263,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/conditional-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:queries/conditional-query-constructs/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| @@ -276,7 +276,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/conditional-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:queries/conditional-query-constructs/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` @@ -292,7 +292,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/conditional-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:queries/conditional-query-constructs/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` @@ -1043,9 +1043,9 @@ RETURN CASE n.prop END ---- -| Extension of the xref::queries/conditional-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::queries/conditional-query-constructs/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/conditional-queries/case.adoc#case-extended-simple[extended version of the simple `CASE`]. +in an xref::queries/conditional-query-constructs/conditional-expressions.adoc#case-extended-simple[extended version of the simple `CASE`]. a| label:functionality[] diff --git a/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc b/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc deleted file mode 100644 index 3ed03b60d..000000000 --- a/modules/ROOT/pages/queries/conditional-queries/conditional-clauses.adoc +++ /dev/null @@ -1,21 +0,0 @@ -= Conditional query constructs (WHEN, THEN) -:page-role: new-2025.02 - -== Syntax - -== Standalone conditional queries - -.Example ----- -WHEN ... -WHEN ... -ELSE ... -// OK ----- - - -== Conditional subqueries - -== Conditional `UNION` queries - -== Rules \ No newline at end of file diff --git a/modules/ROOT/pages/queries/conditional-queries/index.adoc b/modules/ROOT/pages/queries/conditional-queries/index.adoc deleted file mode 100644 index 6862ccf8a..000000000 --- a/modules/ROOT/pages/queries/conditional-queries/index.adoc +++ /dev/null @@ -1,16 +0,0 @@ -= Conditional queries -:page-role: new-2025.02 - -== Syntax - -== Standalone conditional clauses - -=== Sequencing conditional clauses - -== Conditional clauses within subqueries - -== Conditional clauses and `UNION` clauses - -Top level braces - -== Rules diff --git a/modules/ROOT/pages/queries/conditional-queries/case.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-expressions.adoc similarity index 99% rename from modules/ROOT/pages/queries/conditional-queries/case.adoc rename to modules/ROOT/pages/queries/conditional-query-constructs/conditional-expressions.adoc index be2a3420d..c092a5bb5 100644 --- a/modules/ROOT/pages/queries/conditional-queries/case.adoc +++ b/modules/ROOT/pages/queries/conditional-query-constructs/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::conditionals_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/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc new file mode 100644 index 000000000..eecce3c5c --- /dev/null +++ b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc @@ -0,0 +1,108 @@ += Conditional queries (WHEN) +:page-role: new-2025.02 + +The `WHEN` clause in Cypher allows conditional execution of parts of a query. +It enables different query branches or subqueries to run based on specified conditions. + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::conditionals_graph.svg[width="400",role="middle"] + +To recreate the graph, run the following query against an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE + (alice:Person {name:'Alice', age: 38, eyes: 'brown'}), + (bob:Person {name: 'Bob', age: 25, eyes: 'blue'}), + (charlie:Person {name: 'Charlie', age: 53, eyes: 'green'}), + (daniel:Person {name: 'Daniel', eyes: 'brown'}), + (eskil:Person {name: 'Eskil', age: 41, eyes: 'blue'}), + (alice)-[:KNOWS]->(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +---- + +== Syntax + +[source, cypher] +---- + ::= + | | ; + + ::= + , + "UNION" , [ "ALL" | "DISTINCT" ] , ( | ) ; + + ::= + , { } , [ ] ; + + ::= + "WHEN" , , "THEN" , ; + + ::= + "ELSE" , +---- + + +== Standalone conditional queries + +[source, cypher] +---- +WHEN false THEN RETURN 1 AS x +WHEN true THEN RETURN 2 AS x +ELSE RETURN 3 AS x +---- + +== Conditional subqueries + +[source, cypher] +---- + MATCH (n:Person) + OPTIONAL MATCH (n)-[:KNOWS]->(m) + CALL (n,m) { + WHEN m IS NULL THEN { + CREATE (f: Person {name: 'Peter', age: n.age}), (n)-[:KNOWS]->(f) + } + } +---- + +[source, cypher] +---- +MATCH (n:Person) +WHERE EXISTS { + WHEN n.age > 40 THEN { + MATCH (n)<-[:MARRIED]-(x:Person) + RETURN x + } + ELSE { + MATCH (n)<-[:KNOWS]-(x:Person) + RETURN x + } +} + RETURN n.name +---- + +== Conditional `UNION` queries + +[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 +} +---- + +== Rules \ No newline at end of file diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/index.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/index.adoc new file mode 100644 index 000000000..e993a553d --- /dev/null +++ b/modules/ROOT/pages/queries/conditional-query-constructs/index.adoc @@ -0,0 +1,9 @@ += Conditional query constructs + +In Cypher, conditional query constructs enable queries to adapt based on conditions. +The `CASE` expression allows conditional logic to determine values or actions based on evaluated conditions, while the `WHEN` condition enables entire sections of a query or subqueries to execute conditionally. + +For more information, see: + +* xref:queries/conditional-query-constructs/conditional-expressions.adoc[] +* xref:queries/conditional-query-constructs/conditional-queries.adoc[] label:new[Introduced in Neo4j 2025.02] diff --git a/modules/ROOT/pages/queries/index.adoc b/modules/ROOT/pages/queries/index.adoc index ad19c5f55..f26436c9d 100644 --- a/modules/ROOT/pages/queries/index.adoc +++ b/modules/ROOT/pages/queries/index.adoc @@ -7,4 +7,4 @@ It also contains information about Cypher expressions. * xref:queries/concepts.adoc[] * xref:queries/basic.adoc[] * xref:queries/expressions.adoc[] -* xref:queries/conditional-queries/index.adoc[] +* xref:queries/conditional-query-constructs/index.adoc[] From e88693a0db74cfa96b434ac84da2dea585d7d24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:01:42 +0100 Subject: [PATCH 03/32] begin conditional subqueries --- .../conditional-queries.adoc | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc index eecce3c5c..3846612ea 100644 --- a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc @@ -1,8 +1,10 @@ = Conditional queries (WHEN) :page-role: new-2025.02 -The `WHEN` clause in Cypher allows conditional execution of parts of a query. -It enables different query branches or subqueries to run based on specified conditions. +The `WHEN` clause in Cypher allows conditional execution of parts of a query, enabling different query branches or subqueries to run based on specified conditions. +Much like `UNION`, `WHEN` cannot be used as a regular clause in a query (though it can be used as a standalone clause). +Instead, it encloses different query branches or subqueries as a condition for their execution. +In this way, it performs similar a control-flow structure to the `IF` statements of other programming languages. [[example-graph]] == Example graph @@ -28,49 +30,64 @@ CREATE (bob)-[:MARRIED]->(eskil) ---- -== Syntax +== Standalone `WHEN` branches +`WHEN` branches can be used independently of other query parts. +The first predicate that evaluates to `true` will be executed. +If no `WHEN` branches were executed and an `ELSE` branch exists, it is executed. +If no `WHEN` branches evaluates to `true` and no `ELSE` branch is present, no rows are produced. +The following example demonstrates this basic logic: + +.Basic `WHEN` logic [source, cypher] ---- - ::= - | | ; +WHEN false THEN RETURN 1 AS x +WHEN true THEN RETURN 2 AS x +ELSE RETURN 3 AS x +---- - ::= - , - "UNION" , [ "ALL" | "DISTINCT" ] , ( | ) ; +Since the second `WHEN` branch is `true`, it will execute, while the preceding branch (which is `false`) and the succeeding `ELSE` branch will be skipped. - ::= - , { } , [ ] ; +.Result +[role="queryresult",options="header,footer",cols="1* ::= - "WHEN" , , "THEN" , ; +1+d| Rows: 1 +|=== - ::= - "ELSE" , ----- +== Conditional subqueries +`WHEN` can be used inside a subquery to execute operations only when a condition evaluates to `true`. -== Standalone conditional queries +In the below 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`. +.`WHEN` inside a `CALL` subquery [source, cypher] ---- -WHEN false THEN RETURN 1 AS x -WHEN true THEN RETURN 2 AS x -ELSE RETURN 3 AS x +MATCH (n:Person) +OPTIONAL MATCH (n)-[:KNOWS]->(m) +CALL { + WHEN m IS NULL THEN { + MERGE (f: Person {name: 'Peter'}), (n)-[:KNOWS]->(f) + RETURN f, collect(n.name) AS newFriends + } +} +RETURN f.name AS newNode, newFriends ---- -== Conditional subqueries +.Result +[role="queryresult",options="header,footer",cols="2*(m) - CALL (n,m) { - WHEN m IS NULL THEN { - CREATE (f: Person {name: 'Peter', age: n.age}), (n)-[:KNOWS]->(f) - } - } ----- [source, cypher] ---- From a95db766627692632d26da39f44bc1959948bb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:49:19 +0100 Subject: [PATCH 04/32] more --- .../conditional-queries.adoc | 60 ++++++++++++++----- .../ROOT/pages/subqueries/call-subquery.adoc | 7 +++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc index 3846612ea..29c9a97ea 100644 --- a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc @@ -2,7 +2,7 @@ :page-role: new-2025.02 The `WHEN` clause in Cypher allows conditional execution of parts of a query, enabling different query branches or subqueries to run based on specified conditions. -Much like `UNION`, `WHEN` cannot be used as a regular clause in a query (though it can be used as a standalone clause). +Much like `UNION`, `WHEN` cannot be used as a regular clause in a query (though it can be used as standalone branches). Instead, it encloses different query branches or subqueries as a condition for their execution. In this way, it performs similar a control-flow structure to the `IF` statements of other programming languages. @@ -60,17 +60,21 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b == Conditional subqueries `WHEN` can be used inside a subquery to execute operations only when a condition evaluates to `true`. - -In the below 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`. +For example, in a xref:subqueries/call-subquery.adoc[`CALL` subquery], you can use `WHEN` to ensure that particular actions are executed when a certain criteria is met. .`WHEN` inside a `CALL` subquery +===== + +In this example, `WHEN` is used to execute a `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)-[:KNOWS]->(m) CALL { WHEN m IS NULL THEN { - MERGE (f: Person {name: 'Peter'}), (n)-[:KNOWS]->(f) + MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}), (n)-[:KNOWS]->(f) RETURN f, collect(n.name) AS newFriends } } @@ -87,24 +91,52 @@ RETURN f.name AS newNode, newFriends 2+d| Rows: 1 |=== +===== + +.`WHEN` inside an `EXISTS` subquery +===== +Here `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the condition (`n.age > 40`). +If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` is married to. + +.`WHEN` inside `EXISTS` subquery [source, cypher] ---- -MATCH (n:Person) +MATCH (person:Person) WHERE EXISTS { - WHEN n.age > 40 THEN { - MATCH (n)<-[:MARRIED]-(x:Person) - RETURN x - } - ELSE { - MATCH (n)<-[:KNOWS]-(x:Person) - RETURN x - } + WHEN person.age > 40 THEN { + MATCH (person)-[:KNOWS]->(x:Person) + RETURN x AS knows + } + ELSE { + MATCH (person)-[:MARRIED]->(x:Person) + RETURN x AS marriedTo + } } - RETURN n.name +RETURN DISTINCT person.name AS person, + collect(knows.name) AS knows, + collect(marriedTo.name) AS marriedTo ---- +.Result +[role="queryresult",options="header,footer",cols="3* Date: Tue, 21 Jan 2025 16:19:04 +0100 Subject: [PATCH 05/32] add example on call page --- .../conditional-queries.adoc | 2 +- .../ROOT/pages/subqueries/call-subquery.adoc | 44 ++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc index 29c9a97ea..986a7be4c 100644 --- a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc @@ -59,7 +59,7 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b == Conditional subqueries -`WHEN` can be used inside a subquery to execute operations only when a condition evaluates to `true`. +`WHEN` branches can be used inside a subquery to execute operations only when a condition evaluates to `true`. For example, in a xref:subqueries/call-subquery.adoc[`CALL` subquery], you can use `WHEN` to ensure that particular actions are executed when a certain criteria is met. .`WHEN` inside a `CALL` subquery diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc index 7bbe8d13e..9dc603480 100644 --- a/modules/ROOT/pages/subqueries/call-subquery.adoc +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -467,7 +467,6 @@ RETURN p.name AS playerName, team.name AS team Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relationships connected to a `Team`, are returned. -.Result .Result [role="queryresult",options="header,footer",cols="2*m"] |=== @@ -489,7 +488,48 @@ Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relation [[conditional-call]] == Conditional CALL subqueries -`WHEN` can be used inside a `CALL` subquery to execute operations only when a condition evaluates to `true`. +`WHEN` branches can be used inside a `CALL` subquery to execute operations only when a condition evaluates to `true`. + +.Conditional subquery call +===== + +Check a condition (such as players over 30 or not), and execute the relevant action accordingly. +Use a meaningful ELSE clause when the condition isn't met. + +.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 > 30 THEN { + RETURN collect(p.name) AS olderPlayers + } + ELSE { + MATCH (p:Player)-[:PLAYS_FOR]->(t) + WHERE p.age <= 30 + RETURN collect(p.name) AS youngerPlayers + } +} +RETURN t.name AS teamName, olderPlayers, youngerPlayers +---- + +.Result +[role="queryresult",options="header,footer",cols="3*m"] +|=== +| teamName | olderPlayers | youngerPlayers + +| "Team A" | ["Player B"] | ["Player A"] +| "Team B" | ["Player D"] | [] +| "Team C" | [] | ["Player E", "Player F"] + +3+d|Rows: 3 +|=== + + +For more information about conditional subqueries, see xref:queries/conditional-query-constructs/conditional-queries.adoc[Conditional queries]. + +===== [[call-execution-order]] From e0ed7f81b3a399af46ff1257adbc82da204cd825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:52:59 +0100 Subject: [PATCH 06/32] =?UTF-8?q?more=C3=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../conditional-queries.adoc | 35 ++++++++++++------- modules/ROOT/pages/subqueries/collect.adoc | 5 +++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc index 986a7be4c..8420abefd 100644 --- a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc @@ -21,7 +21,7 @@ CREATE (alice:Person {name:'Alice', age: 38, eyes: 'brown'}), (bob:Person {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:Person {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel:Person {name: 'Daniel', eyes: 'brown'}), + (daniel:Person {name: 'Daniel', age: 32, eyes: 'brown'}), (eskil:Person {name: 'Eskil', age: 41, eyes: 'blue'}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), @@ -72,13 +72,14 @@ In this example, `WHEN` is used to execute a `CALL` subquery for each row that t ---- MATCH (n:Person) OPTIONAL MATCH (n)-[:KNOWS]->(m) -CALL { +CALL (*) { WHEN m IS NULL THEN { - MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}), (n)-[:KNOWS]->(f) - RETURN f, collect(n.name) AS newFriends + MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) + MERGE (n)-[:KNOWS]->(f) + RETURN f, n.name AS newFriend } } -RETURN f.name AS newNode, newFriends +RETURN f.name AS newNode, collect(newFriend) AS newFriends ---- .Result @@ -103,22 +104,32 @@ If the condition evaluates to `true`, the subquery fetches people that the `pers .`WHEN` inside `EXISTS` subquery [source, cypher] ---- -MATCH (person:Person) +MATCH (person:Person)-[r]->(connection:Person) WHERE EXISTS { WHEN person.age > 40 THEN { MATCH (person)-[:KNOWS]->(x:Person) - RETURN x AS knows + RETURN x } ELSE { - MATCH (person)-[:MARRIED]->(x:Person) - RETURN x AS marriedTo + MATCH (person)-[:MARRIED]->(y:Person) + RETURN x } } -RETURN DISTINCT person.name AS person, - collect(knows.name) AS knows, - collect(marriedTo.name) AS marriedTo +RETURN person.name AS person, + type(r) AS relationship, + connection.name AS connection ---- + + ++---------------------------------+ +| name | type(r) | z.name | ++---------------------------------+ +| "Eskil" | "KNOWS" | "Peter" | +| "Bob" | "KNOWS" | "Daniel" | +| "Bob" | "MARRIED" | "Eskil" | +| "Daniel" | "KNOWS" | "Peter" | ++---------------------------------+ .Result [role="queryresult",options="header,footer",cols="3* Date: Wed, 22 Jan 2025 16:34:44 +0100 Subject: [PATCH 07/32] big rethink --- modules/ROOT/content-nav.adoc | 13 ++--- .../gql-conformance/supported-mandatory.adoc | 4 +- .../gql-conformance/supported-optional.adoc | 2 +- .../pages/clauses/clause-composition.adoc | 2 +- modules/ROOT/pages/clauses/index.adoc | 4 +- modules/ROOT/pages/clauses/use.adoc | 2 +- modules/ROOT/pages/clauses/where.adoc | 4 +- ...ions-additions-removals-compatibility.adoc | 14 +++--- .../conditional-expressions.adoc | 0 .../expressions-overview.adoc} | 2 +- modules/ROOT/pages/expressions/index.adoc | 1 + modules/ROOT/pages/patterns/reference.adoc | 2 +- .../patterns/variable-length-patterns.adoc | 2 +- .../operators/operators-detail.adoc | 2 +- .../composed-queries/combined-queries.adoc} | 2 +- .../conditional-queries.adoc | 48 +++++++------------ .../pages/queries/composed-queries/index.adoc | 9 ++++ .../conditional-query-constructs/index.adoc | 9 ---- modules/ROOT/pages/queries/index.adoc | 8 ++-- .../ROOT/pages/subqueries/call-subquery.adoc | 4 +- modules/ROOT/pages/syntax/operators.adoc | 6 +-- modules/ROOT/pages/syntax/parsing.adoc | 2 +- .../property-structural-constructed.adoc | 6 +-- 23 files changed, 69 insertions(+), 79 deletions(-) rename modules/ROOT/pages/{queries/conditional-query-constructs => expressions}/conditional-expressions.adoc (100%) rename modules/ROOT/pages/{queries/expressions.adoc => expressions/expressions-overview.adoc} (94%) create mode 100644 modules/ROOT/pages/expressions/index.adoc rename modules/ROOT/pages/{clauses/union.adoc => queries/composed-queries/combined-queries.adoc} (99%) rename modules/ROOT/pages/queries/{conditional-query-constructs => composed-queries}/conditional-queries.adoc (75%) create mode 100644 modules/ROOT/pages/queries/composed-queries/index.adoc delete mode 100644 modules/ROOT/pages/queries/conditional-query-constructs/index.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 2210489ce..2d08c7ecc 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -6,11 +6,9 @@ * xref:queries/index.adoc[] ** xref:queries/concepts.adoc[] ** xref:queries/basic.adoc[] -** xref:queries/expressions.adoc[] -** xref:queries/conditional-query-constructs/index.adoc[] -*** xref:queries/conditional-query-constructs/conditional-expressions.adoc[] -*** xref:queries/conditional-query-constructs/conditional-queries.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[] @@ -34,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/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index f444b2d7c..a5049903c 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -148,7 +148,7 @@ This is currently not available in Cypher. | 20.2 | -| xref:queries/expressions.adoc[] +| xref:expressions/expressions-overview.adoc[] | | 20.3 @@ -159,7 +159,7 @@ In Cypher, current user details can be seen using the link:{neo4j-docs-base-uri} | 20.7 | -| xref:queries/conditional-query-constructs/conditional-expressions.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 77defa1ec..30333330c 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -158,7 +158,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 0ecf72c39..a30260999 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -460,7 +460,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: @@ -531,7 +531,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 a90fd4a9c..69fa94c17 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -162,7 +162,7 @@ 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. +| 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: @@ -284,7 +284,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/conditional-query-constructs/conditional-expressions.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| @@ -297,7 +297,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/conditional-query-constructs/conditional-expressions.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` @@ -313,7 +313,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/conditional-query-constructs/conditional-expressions.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` @@ -1005,7 +1005,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[] @@ -1064,9 +1064,9 @@ RETURN CASE n.prop END ---- -| Extension of the xref::queries/conditional-query-constructs/conditional-expressions.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/conditional-query-constructs/conditional-expressions.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/conditional-query-constructs/conditional-expressions.adoc b/modules/ROOT/pages/expressions/conditional-expressions.adoc similarity index 100% rename from modules/ROOT/pages/queries/conditional-query-constructs/conditional-expressions.adoc rename to modules/ROOT/pages/expressions/conditional-expressions.adoc diff --git a/modules/ROOT/pages/queries/expressions.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc similarity index 94% rename from modules/ROOT/pages/queries/expressions.adoc rename to modules/ROOT/pages/expressions/expressions-overview.adoc index f4ba6f036..e8b90e44f 100644 --- a/modules/ROOT/pages/queries/expressions.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -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..b5e249dbe --- /dev/null +++ b/modules/ROOT/pages/expressions/index.adoc @@ -0,0 +1 @@ += Expressions \ No newline at end of file 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 875acb6a5..5721d1682 100644 --- a/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc +++ b/modules/ROOT/pages/planning-and-tuning/operators/operators-detail.adoc @@ -4545,7 +4545,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 99% rename from modules/ROOT/pages/clauses/union.adoc rename to modules/ROOT/pages/queries/composed-queries/combined-queries.adoc index b193a7786..a6a4fc7c6 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. diff --git a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc similarity index 75% rename from modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc rename to modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 8420abefd..e39992d59 100644 --- a/modules/ROOT/pages/queries/conditional-query-constructs/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -1,4 +1,4 @@ -= Conditional queries (WHEN) += Conditional queries (`WHEN...THEN...ELSE`) :page-role: new-2025.02 The `WHEN` clause in Cypher allows conditional execution of parts of a query, enabling different query branches or subqueries to run based on specified conditions. @@ -22,7 +22,7 @@ CREATE (bob:Person {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:Person {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:Person {name: 'Daniel', age: 32, eyes: 'brown'}), - (eskil:Person {name: 'Eskil', age: 41, eyes: 'blue'}), + (eskil:Person {name: 'Eskil', age: 39, eyes: 'blue'}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), @@ -71,7 +71,7 @@ In this example, `WHEN` is used to execute a `CALL` subquery for each row that t [source, cypher] ---- MATCH (n:Person) -OPTIONAL MATCH (n)-[:KNOWS]->(m) +OPTIONAL MATCH (n)-[:KNOWS]->(m:Person) CALL (*) { WHEN m IS NULL THEN { MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) @@ -79,7 +79,8 @@ CALL (*) { RETURN f, n.name AS newFriend } } -RETURN f.name AS newNode, collect(newFriend) AS newFriends +RETURN f.name AS newNode, + collect(newFriend) AS newFriends ---- .Result @@ -95,7 +96,7 @@ RETURN f.name AS newNode, collect(newFriend) AS newFriends ===== -.`WHEN` inside an `EXISTS` subquery +.Conditional `EXISTS` subquery ===== Here `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the condition (`n.age > 40`). @@ -104,50 +105,35 @@ If the condition evaluates to `true`, the subquery fetches people that the `pers .`WHEN` inside `EXISTS` subquery [source, cypher] ---- -MATCH (person:Person)-[r]->(connection:Person) +MATCH (n:Person) WHERE EXISTS { - WHEN person.age > 40 THEN { - MATCH (person)-[:KNOWS]->(x:Person) + WHEN n.age > 40 THEN { + MATCH (n)-[:KNOWS]->(x:Person) RETURN x } ELSE { - MATCH (person)-[:MARRIED]->(y:Person) + MATCH (n)-[:MARRIED]->(x:Person) RETURN x } } -RETURN person.name AS person, - type(r) AS relationship, - connection.name AS connection +RETURN n.name AS names ---- +Since each condition is satisfied by a distinct node, the query only returns two rows. - -+---------------------------------+ -| name | type(r) | z.name | -+---------------------------------+ -| "Eskil" | "KNOWS" | "Peter" | -| "Bob" | "KNOWS" | "Daniel" | -| "Bob" | "MARRIED" | "Eskil" | -| "Daniel" | "KNOWS" | "Peter" | -+---------------------------------+ .Result -[role="queryresult",options="header,footer",cols="3* Date: Thu, 23 Jan 2025 16:14:51 +0100 Subject: [PATCH 08/32] gql and additions info, and much more --- .../pages/appendix/gql-conformance/index.adoc | 4 +- .../gql-conformance/supported-mandatory.adoc | 10 ++ ...ions-additions-removals-compatibility.adoc | 46 +++++++++ .../composed-queries/combined-queries.adoc | 37 +++++++- .../composed-queries/conditional-queries.adoc | 93 +++++++++++++++---- .../pages/queries/composed-queries/index.adoc | 5 +- 6 files changed, 174 insertions(+), 21 deletions(-) diff --git a/modules/ROOT/pages/appendix/gql-conformance/index.adoc b/modules/ROOT/pages/appendix/gql-conformance/index.adoc index 438f89042..cb494d7d6 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*: 23 January October 2025 + +*Neo4j version*: 2025.02 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 a5049903c..107ddf169 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`] and +| + | 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`] diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 69fa94c17..a649965c8 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -181,6 +181,52 @@ Note that escaping graph names within the graph functions string argument is not |=== | 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) + } + } +---- + +| `WHEN...THEN...ELSE` constructs are now available in Cypher, enabling the composition of conditional queries. +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[] diff --git a/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc b/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc index a6a4fc7c6..a528abf6a 100644 --- a/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/combined-queries.adoc @@ -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.02] +[[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*(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), - (bob)-[:MARRIED]->(eskil) + (bob)-[:LOVES]->(eskil) ---- -== Standalone `WHEN` branches +== Standalone `WHEN` branches and basic logic -`WHEN` branches can be used independently of other query parts. +`WHEN`/`ELSE` branches can be used independently of other clauses. The first predicate that evaluates to `true` will be executed. If no `WHEN` branches were executed and an `ELSE` branch exists, it is executed. If no `WHEN` branches evaluates to `true` and no `ELSE` branch is present, no rows are produced. @@ -57,9 +56,28 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b 1+d| Rows: 1 |=== +[[query-branches-column-rules]] +== Branch consistency: columns and names + +Similar to `UNION`, the number and names of the columns must be identical in all branches included in a `WHEN`/`ELSE` branch. + +.Not allowed: using different column names in different branches +[source, cypher, role=test-fail] +---- +WHEN true THEN RETURN 2 AS x +ELSE RETURN 3 AS y +---- + +.Not allowed: returning different number of columns in different branches +[source, cypher, role=test-fail] +---- +WHEN true THEN RETURN 2 AS x +ELSE RETURN 3 AS y +---- + == Conditional subqueries -`WHEN` branches can be used inside a subquery to execute operations only when a condition evaluates to `true`. +`WHEN` branches can be used inside a xref:subqueries/index.adoc[subquery] to execute operations only when a condition evaluates to `true`. For example, in a xref:subqueries/call-subquery.adoc[`CALL` subquery], you can use `WHEN` to ensure that particular actions are executed when a certain criteria is met. .`WHEN` inside a `CALL` subquery @@ -75,7 +93,7 @@ OPTIONAL MATCH (n)-[:KNOWS]->(m:Person) CALL (*) { WHEN m IS NULL THEN { MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) - MERGE (n)-[:KNOWS]->(f) + MERGE (n)-[:KNOWS]->(f: Person {name: 'Peter'}) RETURN f, n.name AS newFriend } } @@ -112,30 +130,36 @@ WHERE EXISTS { RETURN x } ELSE { - MATCH (n)-[:MARRIED]->(x:Person) + MATCH (n)-[:LOVES]->(x:Person) RETURN x } } -RETURN n.name AS names +RETURN n.name AS names, n.age AS age, exists((n)-[:LOVES]->()) as hasLovesRel; ---- Since each condition is satisfied by a distinct node, the query only returns two rows. .Result -[role="queryresult",options="header,footer",cols="1*(m:Person) +CALL (*) { + WHEN m IS NULL THEN { + MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) + MERGE (n)-[:KNOWS]->(f) + RETURN f, n.name AS newFriend + } +} +RETURN f.name AS newNode, + collect(newFriend) AS newFriends +} +UNION +{ +MATCH (n:Person) +OPTIONAL MATCH (n)-[:LOVES]->(m:Person) +CALL (*) { + WHEN m IS NULL THEN { + MERGE (f: Person {name: 'John'}) + MERGE (n)-[:KNOWS]->(f) + RETURN f, n.name AS newFriend + } +} +RETURN f.name AS newNode, + collect(newFriend) AS newFriends + + + == Rules \ No newline at end of file diff --git a/modules/ROOT/pages/queries/composed-queries/index.adoc b/modules/ROOT/pages/queries/composed-queries/index.adoc index c077b5bc4..ab4240cc1 100644 --- a/modules/ROOT/pages/queries/composed-queries/index.adoc +++ b/modules/ROOT/pages/queries/composed-queries/index.adoc @@ -1,7 +1,8 @@ = Composed queries -Cypher contains operators that enable the composition of separate query branches. -These operators manage the logic and execution flow of queries and, as such, cannot be used as regular clauses. +Cypher contains operators that enable the composition of separate query branches: `UNION` and `WHEN`. +`UNION` allow for the combination of different queries into the same result, while `WHEN` enables the compisiton of conditional queries, where different branches can be made to execute depending on a set criteria. +As such, `UNION` and `WHEN` manage in different ways the execution flow and logic of queries and, therefore, cannot be used as regular clauses. For more information, see: From 83a0bbf19cc651b3757559e3aa8320e502eba82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:04:41 +0100 Subject: [PATCH 09/32] more --- .../composed-queries/conditional-queries.adoc | 88 ++++++++++++------- .../ROOT/pages/subqueries/call-subquery.adoc | 42 ++++----- modules/ROOT/pages/subqueries/collect.adoc | 9 ++ modules/ROOT/pages/subqueries/count.adoc | 8 ++ 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 2662f3ea0..b35f383dc 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -57,7 +57,7 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b |=== [[query-branches-column-rules]] -== Branch consistency: columns and names +== Branch consistency: column numbers and names Similar to `UNION`, the number and names of the columns must be identical in all branches included in a `WHEN`/`ELSE` branch. @@ -71,8 +71,8 @@ ELSE RETURN 3 AS y .Not allowed: returning different number of columns in different branches [source, cypher, role=test-fail] ---- -WHEN true THEN RETURN 2 AS x -ELSE RETURN 3 AS y +WHEN true THEN RETURN 2 AS x, 3 AS y +ELSE RETURN 3 AS x ---- == Conditional subqueries @@ -157,7 +157,8 @@ Since each condition is satisfied by a distinct node, the query only returns two Conditional queries can also be combined using `UNION [DISTINCT]` or `UNION ALL` (the former alternative removes duplicates from the result set, the latter does not). -If the conditional query begins with `WHEN`, it must be enclosed with curly braces, `{}`. +If the conditional query begins with `WHEN` and involves `UNION`, the `WHEN` branches must be enclosed within top-level curly braces, `{}`. +(Such top-level braces can also be used to xref:queries/composed-queries/combined-queries.adoc[combine `UNION` and `UNION ALL`].) .Combining conditional queries with `UNION` [source, cypher] @@ -175,41 +176,68 @@ UNION } ---- -Similarly, if `WHEN` branches are combined with more than one `UNION` operator, curly operators must be used a +.Result +[role="queryresult",options="header,footer",cols="1*(m:Person) -CALL (*) { - WHEN m IS NULL THEN { - MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) - MERGE (n)-[:KNOWS]->(f) - RETURN f, n.name AS newFriend - } -} -RETURN f.name AS newNode, - collect(newFriend) AS newFriends -} -UNION -{ -MATCH (n:Person) -OPTIONAL MATCH (n)-[:LOVES]->(m:Person) -CALL (*) { - WHEN m IS NULL THEN { - MERGE (f: Person {name: 'John'}) - MERGE (n)-[:KNOWS]->(f) - RETURN f, n.name AS newFriend - } +CALL (n) { + OPTIONAL MATCH (n)-[r:LOVES]->(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 < 30 THEN { + RETURN n.name AS person, "Younger person" AS message + } + ELSE { + RETURN n.name AS person, "Older person" AS message + } + } + RETURN person, message } -RETURN f.name AS newNode, - collect(newFriend) AS newFriends +RETURN person, collect(message) as status +---- +.Result +[role="queryresult",options="header,footer",cols="2* Conditional subqueries]. -.Conditional subquery call +.Conditional `CALL` subqueries ===== -Check a condition (such as players over 30 or not), and execute the relevant action accordingly. -Use a meaningful ELSE clause when the condition isn't met. +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 > 30 THEN { - RETURN collect(p.name) AS olderPlayers - } +CALL (*) { + WHEN p.age > 25 THEN { + SET p.ageGroup = "Senior" + RETURN p.name AS player, p.ageGroup AS ageGroup + } ELSE { - MATCH (p:Player)-[:PLAYS_FOR]->(t) - WHERE p.age <= 30 - RETURN collect(p.name) AS youngerPlayers + SET p.ageGroup = "Junior" + RETURN p.name AS player, p.ageGroup AS ageGroup } } -RETURN t.name AS teamName, olderPlayers, youngerPlayers +RETURN player, ageGroup ---- .Result -[role="queryresult",options="header,footer",cols="3*m"] +[role="queryresult",options="header,footer",cols="2*m"] |=== -| teamName | olderPlayers | youngerPlayers +| player | ageGroup -| "Team A" | ["Player B"] | ["Player A"] -| "Team B" | ["Player D"] | [] -| "Team C" | [] | ["Player E", "Player F"] +| "Player A" | "Junior" +| "Player B" | "Junior" +| "Player D" | "Senior" +| "Player E" | "Junior" +| "Player F" | "Senior" -3+d|Rows: 3 +2+d|Rows: 5 |=== - -For more information about conditional subqueries, see xref:queries/composed-queries/conditional-queries.adoc[Conditional queries]. - ===== diff --git a/modules/ROOT/pages/subqueries/collect.adoc b/modules/ROOT/pages/subqueries/collect.adoc index f1af2d2a0..3139448ec 100644 --- a/modules/ROOT/pages/subqueries/collect.adoc +++ b/modules/ROOT/pages/subqueries/collect.adoc @@ -311,6 +311,15 @@ RETURN COLLECT { == Conditional COLLECT subqueries +[role=label--new-2025.02] +[[conditional-call]] +== Conditional COLLECT subqueries + +`WHEN` operators can be used inside a `COLLECT` subquery to execute branches only when a condition evaluates to `true`. +Note that the names and 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]. + + [[collect-rules]] == Rules diff --git a/modules/ROOT/pages/subqueries/count.adoc b/modules/ROOT/pages/subqueries/count.adoc index 18ef3fe6a..d1cb70121 100644 --- a/modules/ROOT/pages/subqueries/count.adoc +++ b/modules/ROOT/pages/subqueries/count.adoc @@ -275,6 +275,14 @@ RETURN person.name AS name 1+d|Rows: 1 |=== +[role=label--new-2025.02] +[[conditional-call]] +== Conditional COUNT subqueries + +`WHEN` operators can be used inside a `COUNT` subquery to execute branches only when a condition evaluates to `true`. +Note that the names and 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]. + [[count-rules]] == Rules From 13ff1540635f9b527030f81bcefce0fe2f43a6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:31:34 +0100 Subject: [PATCH 10/32] more --- ...ions-additions-removals-compatibility.adoc | 2 +- .../composed-queries/conditional-queries.adoc | 153 ++++++++++++------ .../pages/queries/composed-queries/index.adoc | 6 +- modules/ROOT/pages/subqueries/collect.adoc | 7 +- 4 files changed, 108 insertions(+), 60 deletions(-) diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index a649965c8..7e639736b 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -204,7 +204,7 @@ ELSE RETURN 3 AS x } ---- -| `WHEN...THEN...ELSE` constructs are now available in Cypher, enabling the composition of conditional queries. +| Introduction of the `WHEN` operator which, together with `THEN` and `ELSE`, allows for the composition of conditional queries in Cypher. For more information, see xref:queries/composed-queries/conditional-queries.adoc[]. a| diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index b35f383dc..fb3dd4c77 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -1,9 +1,9 @@ -= Conditional queries (`WHEN...THEN...ELSE`) += Conditional queries (`WHEN`) :page-role: new-2025.02 -`WHEN`...`THEN`...`ELSE` constructs enables different query branches or subqueries to run based on specified conditions. +`WHEN`, together with `THEN` and `ELSE`, enables different branches of a query to execute based on specified conditions. In this way, it performs similar a control-flow mechanism to the `IF` statements found in other programming languages. -Much like `UNION`, `WHEN` cannot be used as a regular clause in a query (though it can be used as standalone branches). +`WHEN` constructs can be used as standalone branches, as part of subqueries, or combined `UNION` queries, but it cannot be used as a regular clause in a query. [[example-graph]] == Example graph @@ -17,24 +17,24 @@ To recreate the graph, run the following query against an empty Neo4j database: [source, cypher, role=test-setup] ---- CREATE - (alice:Person {name:'Alice', age: 38, eyes: 'brown'}), - (bob:Person {name: 'Bob', age: 25, eyes: 'blue'}), - (charlie:Person {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel:Person {name: 'Daniel', age: 32, eyes: 'brown'}), - (eskil:Person {name: 'Eskil', age: 39, eyes: 'blue'}), - (alice)-[:KNOWS]->(bob), - (alice)-[:KNOWS]->(charlie), - (bob)-[:KNOWS]->(daniel), - (charlie)-[:KNOWS]->(daniel), - (bob)-[:LOVES]->(eskil) + (alice:Person {name:'Alice', age: 65}), + (bob:Person {name: 'Bob', age: 25}), + (charlie:Person {name: 'Charlie', age: 61}), + (daniel:Person {name: 'Daniel', age: 39}), + (eskil:Person {name: 'Eskil', age: 39}), + (bob)-[:WORKS_FOR]->(alice), + (alice)-[:WORKS_FOR]->(daniel), + (charlie)-[:WORKS_FOR]->(daniel), + (bob)-[:LOVES]->(eskil), + (charlie)-[:LOVES]->(alice) ---- == Standalone `WHEN` branches and basic logic -`WHEN`/`ELSE` branches can be used independently of other clauses. +`WHEN` branches can be used independently of other clauses. The first predicate that evaluates to `true` will be executed. -If no `WHEN` branches were executed and an `ELSE` branch exists, it is executed. -If no `WHEN` branches evaluates to `true` and no `ELSE` branch is present, no rows are produced. +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 example demonstrates this basic logic: .Basic `WHEN` logic @@ -57,9 +57,9 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b |=== [[query-branches-column-rules]] -== Branch consistency: column numbers and names +=== Branch consistency: column numbers and names -Similar to `UNION`, the number and names of the columns must be identical in all branches included in a `WHEN`/`ELSE` branch. +Similar to `UNION`, the number and names of the columns must be identical in all parts of a `WHEN`/`ELSE` construct. .Not allowed: using different column names in different branches [source, cypher, role=test-fail] @@ -77,34 +77,35 @@ ELSE RETURN 3 AS x == Conditional subqueries -`WHEN` branches can be used inside a xref:subqueries/index.adoc[subquery] to execute operations only when a condition evaluates to `true`. -For example, in a xref:subqueries/call-subquery.adoc[`CALL` subquery], you can use `WHEN` to ensure that particular actions are executed when a certain criteria is met. +`WHEN` can be used inside a xref:subqueries/index.adoc[subquery] to execute a set of operations only when a condition evaluates to `true`. -.`WHEN` inside a `CALL` subquery +.Conditional `CALL` subquery ===== -In this example, `WHEN` is used to execute a `CALL` subquery for each row that the condition (`m IS NULL`) evaluates to `true`. +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)-[:KNOWS]->(m:Person) +OPTIONAL MATCH (n)-[:WORKS_FOR]->(m:Person) CALL (*) { - WHEN m IS NULL THEN { - MERGE (f: Person {name: 'Peter', age: 36, eyes: 'green'}) - MERGE (n)-[:KNOWS]->(f: Person {name: 'Peter'}) - RETURN f, n.name AS newFriend - } + WHEN m IS NULL THEN { + MERGE (f: Person {name: 'Peter', age: 36}) + MERGE (n)-[:WORKS_FOR]->(f) + RETURN f, n.name AS newFriend + } } -RETURN f.name AS newNode, - collect(newFriend) AS newFriends +RETURN f.name AS employer, + collect(newFriend) AS employees ---- +Because only `Daniel` and `Eskil` had no outgoing `KNOWS` relationships, they have now been connected as acquaintances with the new `Peter` node. + .Result [role="queryresult",options="header,footer",cols="2* 40`). +In this example, `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the condition (`n.age > 40`). If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` is married to. +[NOTE] +Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery is not available to the outer scope (the same is true for xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). + .`WHEN` inside `EXISTS` subquery -[source, cypher] +[source, cypher]xxs ---- MATCH (n:Person) WHERE EXISTS { WHEN n.age > 40 THEN { - MATCH (n)-[:KNOWS]->(x:Person) + MATCH (n)-[:WORKS_FOR]->(x:Person) RETURN x } ELSE { @@ -134,7 +138,7 @@ WHERE EXISTS { RETURN x } } -RETURN n.name AS names, n.age AS age, exists((n)-[:LOVES]->()) as hasLovesRel; +RETURN n.name AS name, n.age AS age, exists((n)-[:LOVES]->()) as hasLovesRel ---- Since each condition is satisfied by a distinct node, the query only returns two rows. @@ -144,7 +148,8 @@ Since each condition is satisfied by a distinct node, the query only returns two |=== | names | age | hasLovesRel -| "Charlie" | 53 | FALSE +| "Alice" | 65 | FALSE +| "Charlie" | 61 | TRUE | "Bob" | 25 | TRUE 3+d| Rows: 2 @@ -152,15 +157,69 @@ Since each condition is satisfied by a distinct node, the query only returns two ===== +.Chaining conditional subqueries +===== + +Disjointed conditional subqueries be chained in succession, each executing depending on a set of defined condition. + +This example uses conditional logic to assign age groups to individuals. +It then collects the names of their managers (who they `WORK_FOR`), tagging them as "Older manager" or "Younger manager" based on their age relative to the individual. + +.Combining several conditional `CALL` subqueries +[source, cypher] +---- +MATCH (n:Person)-[r:WORKS_FOR]->(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, "Older manager"]) AS manager + } + ELSE { + RETURN collect([m.name, "Younger manager"]) AS manager + } +} +RETURN n.name AS name, ageGroup, manager +---- + +.Result +[role="queryresult",options="header,footer",cols="3* Conditional subqueries]. From 6e6da66acaa1f1688c4dd71d020550254cb1b363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:49:16 +0100 Subject: [PATCH 11/32] more and more --- ...{conditionals_graph.svg => case_graph.svg} | 0 .../ROOT/images/conditional_query_graph.svg | 1 + .../expressions/conditional-expressions.adoc | 2 +- modules/ROOT/pages/expressions/index.adoc | 10 +- .../composed-queries/conditional-queries.adoc | 92 ++++++++++++------- .../ROOT/pages/subqueries/call-subquery.adoc | 4 +- modules/ROOT/pages/subqueries/collect.adoc | 42 +++++++-- modules/ROOT/pages/subqueries/count.adoc | 7 -- .../ROOT/pages/subqueries/existential.adoc | 40 ++++++++ 9 files changed, 145 insertions(+), 53 deletions(-) rename modules/ROOT/images/{conditionals_graph.svg => case_graph.svg} (100%) create mode 100644 modules/ROOT/images/conditional_query_graph.svg diff --git a/modules/ROOT/images/conditionals_graph.svg b/modules/ROOT/images/case_graph.svg similarity index 100% rename from modules/ROOT/images/conditionals_graph.svg rename to modules/ROOT/images/case_graph.svg diff --git a/modules/ROOT/images/conditional_query_graph.svg b/modules/ROOT/images/conditional_query_graph.svg new file mode 100644 index 000000000..98102e93a --- /dev/null +++ b/modules/ROOT/images/conditional_query_graph.svg @@ -0,0 +1 @@ +WORKS_FORWORKS_FORWORKS_FOR:LOVES:LOVESPersonname:'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/expressions/conditional-expressions.adoc b/modules/ROOT/pages/expressions/conditional-expressions.adoc index c092a5bb5..9c6d93c21 100644 --- a/modules/ROOT/pages/expressions/conditional-expressions.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::conditionals_graph.svg[width="400",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/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index b5e249dbe..144420147 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -1 +1,9 @@ -= Expressions \ No newline at end of file += 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/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index fb3dd4c77..959f637b6 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -1,16 +1,15 @@ = Conditional queries (`WHEN`) :page-role: new-2025.02 -`WHEN`, together with `THEN` and `ELSE`, enables different branches of a query to execute based on specified conditions. +`WHEN`, together with `THEN` and `ELSE`, enables different branches of a query to execute based on certain conditions. In this way, it performs similar a control-flow mechanism to the `IF` statements found in other programming languages. -`WHEN` constructs can be used as standalone branches, as part of subqueries, or combined `UNION` queries, but it cannot be used as a regular clause in a query. [[example-graph]] == Example graph The following graph is used for the examples below: -image::conditionals_graph.svg[width="400",role="middle"] +image::conditional_query_graph.svg[width="600",role="middle"] To recreate the graph, run the following query against an empty Neo4j database: @@ -29,15 +28,16 @@ CREATE (charlie)-[:LOVES]->(alice) ---- +[[standalone-when-logic]] == Standalone `WHEN` branches and basic logic `WHEN` branches can be used independently of other clauses. The first 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 example demonstrates this basic logic: +The following example demonstrates this logic: -.Basic `WHEN` logic +.`WHEN` logic [source, cypher] ---- WHEN false THEN RETURN 1 AS x @@ -56,8 +56,8 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b 1+d| Rows: 1 |=== -[[query-branches-column-rules]] -=== Branch consistency: column numbers and names +[[when-rules]] +== Rules Similar to `UNION`, the number and names of the columns must be identical in all parts of a `WHEN`/`ELSE` construct. @@ -75,13 +75,29 @@ WHEN true THEN RETURN 2 AS x, 3 AS y ELSE RETURN 3 AS x ---- +`WHEN` can also not positioned as a regular clause in a query. +For example, it cannot immediately succeed a `MATCH` clause. + +.Not allowed: using `WHEN` constructs as regular clauses +[source, cypher, role=test-fail] +---- +MATCH (n)-[:WORKS_FOR]->(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 be either be placed within a subquery and/or on different sides of combined `UNION` queries. + == Conditional subqueries -`WHEN` can be used inside a xref:subqueries/index.adoc[subquery] to execute a set of operations only when a condition evaluates to `true`. +`WHEN` can be used inside one or several chained xref:subqueries/index.adoc[subqueries] to execute a set of operations only when a specified condition evaluates to `true`. .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 @@ -96,16 +112,16 @@ CALL (*) { RETURN f, n.name AS newFriend } } -RETURN f.name AS employer, +RETURN f.name AS manager, collect(newFriend) AS employees ---- -Because only `Daniel` and `Eskil` had no outgoing `KNOWS` relationships, they have now been connected as acquaintances with the new `Peter` node. +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* 40`). If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` is married to. @@ -125,7 +140,7 @@ If the condition evaluates to `true`, the subquery fetches people that the `pers Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery is not available to the outer scope (the same is true for xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). .`WHEN` inside `EXISTS` subquery -[source, cypher]xxs +[source, cypher] ---- MATCH (n:Person) WHERE EXISTS { @@ -138,7 +153,9 @@ WHERE EXISTS { RETURN x } } -RETURN n.name AS name, n.age AS age, exists((n)-[:LOVES]->()) as hasLovesRel +RETURN n.name AS name, + n.age AS age, + exists((n)-[:LOVES]->()) AS hasLovesRel ---- Since each condition is satisfied by a distinct node, the query only returns two rows. @@ -163,12 +180,13 @@ Since each condition is satisfied by a distinct node, the query only returns two Disjointed conditional subqueries be chained in succession, each executing depending on a set of defined condition. This example uses conditional logic to assign age groups to individuals. -It then collects the names of their managers (who they `WORK_FOR`), tagging them as "Older manager" or "Younger manager" based on their age relative to the individual. +It then collects the names and age grouping of their managers (who they `WORK_FOR`). -.Combining several conditional `CALL` subqueries +.Chaining several conditional `CALL` subqueries [source, cypher] ---- -MATCH (n:Person)-[r:WORKS_FOR]->(m:Person) +MATCH (n:Person) +OPTIONAL MATCH (n)-[r:WORKS_FOR]->(m:Person) CALL (*) { WHEN n.age > 60 THEN { SET n.ageGroup = 'Veteran' @@ -185,27 +203,29 @@ CALL (*) { } CALL (*) { WHEN m.age > n.age THEN { - RETURN collect([m.name, "Older manager"]) AS manager + RETURN collect([m.name, m.ageGroup]) AS manager } ELSE { - RETURN collect([m.name, "Younger manager"]) AS manager + RETURN collect([m.name, m.ageGroup]) AS manager } } RETURN n.name AS name, ageGroup, manager ---- + .Result [role="queryresult",options="header,footer",cols="3* Conditional subqueries]. .Conditional `CALL` subqueries diff --git a/modules/ROOT/pages/subqueries/collect.adoc b/modules/ROOT/pages/subqueries/collect.adoc index 9090d24ea..2a8362f55 100644 --- a/modules/ROOT/pages/subqueries/collect.adoc +++ b/modules/ROOT/pages/subqueries/collect.adoc @@ -74,6 +74,39 @@ RETURN person.name as name, COLLECT { 2+d|Rows: 3 |=== +== Conditional `COLLECT` subquery + +`WHEN` can be used inside `COLLECT` subqueries to execute branches only when a condition 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]. - - [[collect-rules]] == Rules diff --git a/modules/ROOT/pages/subqueries/count.adoc b/modules/ROOT/pages/subqueries/count.adoc index d1cb70121..e3294db26 100644 --- a/modules/ROOT/pages/subqueries/count.adoc +++ b/modules/ROOT/pages/subqueries/count.adoc @@ -275,13 +275,6 @@ RETURN person.name AS name 1+d|Rows: 1 |=== -[role=label--new-2025.02] -[[conditional-call]] -== Conditional COUNT subqueries - -`WHEN` operators can be used inside a `COUNT` subquery to execute branches only when a condition evaluates to `true`. -Note that the names and 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]. [[count-rules]] == Rules diff --git a/modules/ROOT/pages/subqueries/existential.adoc b/modules/ROOT/pages/subqueries/existential.adoc index e2b3399c1..45d76cba5 100644 --- a/modules/ROOT/pages/subqueries/existential.adoc +++ b/modules/ROOT/pages/subqueries/existential.adoc @@ -73,6 +73,46 @@ RETURN person.name AS name 1+d|Rows: 1 |=== +[role=label--new-2025.02] +[[conditional-exists]] +== Conditional `EXISTS` subqueries + +`WHEN` can be used inside `EXISTS` subqueries to execute branches only when a condition 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]. + +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* Date: Tue, 28 Jan 2025 11:55:37 +0100 Subject: [PATCH 12/32] update --- .../pages/queries/composed-queries/conditional-queries.adoc | 1 + modules/ROOT/pages/queries/composed-queries/index.adoc | 1 + modules/ROOT/pages/subqueries/call-subquery.adoc | 2 +- modules/ROOT/pages/subqueries/count.adoc | 1 - 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 959f637b6..8213bdb22 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -1,4 +1,5 @@ = Conditional queries (`WHEN`) +:description: Information about how to use `WHEN` to construct conditional queries in Cypher. :page-role: new-2025.02 `WHEN`, together with `THEN` and `ELSE`, enables different branches of a query to execute based on certain conditions. diff --git a/modules/ROOT/pages/queries/composed-queries/index.adoc b/modules/ROOT/pages/queries/composed-queries/index.adoc index 40638fe76..eee96a4af 100644 --- a/modules/ROOT/pages/queries/composed-queries/index.adoc +++ b/modules/ROOT/pages/queries/composed-queries/index.adoc @@ -1,4 +1,5 @@ = 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. diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc index 645b4f992..69497c20e 100644 --- a/modules/ROOT/pages/subqueries/call-subquery.adoc +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -486,7 +486,7 @@ Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relation [role=label--new-2025.02] [[conditional-call]] -== Conditional CALL subqueries +== Conditional `CALL` subqueries `WHEN` can be used inside `CALL` subqueries to execute branches only when a condition evaluates to `true`. Note that the names and the number of columns returned by the different `WHEN` branches must be identical. diff --git a/modules/ROOT/pages/subqueries/count.adoc b/modules/ROOT/pages/subqueries/count.adoc index e3294db26..18ef3fe6a 100644 --- a/modules/ROOT/pages/subqueries/count.adoc +++ b/modules/ROOT/pages/subqueries/count.adoc @@ -275,7 +275,6 @@ RETURN person.name AS name 1+d|Rows: 1 |=== - [[count-rules]] == Rules From 24bd2f4709d8e8a0b205b5f99d2f11e1efc99f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:15:55 +0100 Subject: [PATCH 13/32] minor updates --- .../appendix/gql-conformance/supported-mandatory.adoc | 2 +- .../deprecations-additions-removals-compatibility.adoc | 10 +++++++--- .../queries/composed-queries/conditional-queries.adoc | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index 107ddf169..3504f299e 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -30,7 +30,7 @@ Cypher supports the boolean type predicate for `TRUE`, `FALSE`, and `NULL` but d | 9.1 | -| xref:queries/composed-queries/combined-queries.adoc#combining-union-and-union-all[Combining `UNION` and `UNION ALL`] and +| xref:queries/composed-queries/combined-queries.adoc#combining-union-and-union-all[Combining `UNION` and `UNION ALL`] | | 13.2 diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 7e639736b..1188bfc65 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -163,7 +163,7 @@ USE graph.byName('tom`s-database') USE graph.propertiesByName('database.with.dot') ---- | 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. +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: @@ -199,12 +199,16 @@ ELSE RETURN 3 AS x OPTIONAL MATCH (n)-[:KNOWS]->(m) CALL (*) { WHEN m IS NULL THEN { - CREATE (f: Person {name: 'Peter', age: n.age}), (n)-[:KNOWS]->(f) + 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 the `WHEN` operator which, together with `THEN` and `ELSE`, allows for the composition of conditional queries in Cypher. +| 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| diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 8213bdb22..2c0ac70cc 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -110,11 +110,11 @@ CALL (*) { WHEN m IS NULL THEN { MERGE (f: Person {name: 'Peter', age: 36}) MERGE (n)-[:WORKS_FOR]->(f) - RETURN f, n.name AS newFriend + RETURN f, n.name AS employee } } RETURN f.name AS manager, - collect(newFriend) AS employees + 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. From 3fe693ff8bbdee69670b586cbae4637bbe7081ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:12:43 +0100 Subject: [PATCH 14/32] fix image etc --- modules/ROOT/images/conditional_query_graph.svg | 2 +- .../ROOT/pages/expressions/expressions-overview.adoc | 6 +++--- .../queries/composed-queries/conditional-queries.adoc | 11 +++++++---- modules/ROOT/pages/queries/index.adoc | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/ROOT/images/conditional_query_graph.svg b/modules/ROOT/images/conditional_query_graph.svg index 98102e93a..4e3d7b1a0 100644 --- a/modules/ROOT/images/conditional_query_graph.svg +++ b/modules/ROOT/images/conditional_query_graph.svg @@ -1 +1 @@ -WORKS_FORWORKS_FORWORKS_FOR:LOVES:LOVESPersonname:'Alice'age:65Personname:'Bob'age:25Personname:'Charlie'age:61Personname:'Daniel'age:39Personname:'Eskil'age:39 \ No newline at end of file +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/expressions/expressions-overview.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc index e8b90e44f..e45863e9f 100644 --- a/modules/ROOT/pages/expressions/expressions-overview.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 diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 2c0ac70cc..8a5e600b6 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -3,14 +3,17 @@ :page-role: new-2025.02 `WHEN`, together with `THEN` and `ELSE`, enables different branches of a query to execute based on certain conditions. -In this way, it performs similar a control-flow mechanism to the `IF` statements found in other programming languages. +In this way, it performs similar a control-flow mechanism to the `IF` statement in other programming languages. + +[NOTE] +For information about using `WHEN` branches in expressions, see xref:expressions/conditional-expressions.adoc[] [[example-graph]] == Example graph The following graph is used for the examples below: -image::conditional_query_graph.svg[width="600",role="middle"] +image::conditional_query_graph.svg[width="700",role="middle"] To recreate the graph, run the following query against an empty Neo4j database: @@ -293,10 +296,10 @@ CALL (n) { UNION CALL (*) { WHEN n.age < 40 THEN { - RETURN n.name AS person, "Younger person" AS message + RETURN n.name AS person, "Younger person" AS message } ELSE { - RETURN n.name AS person, "Older person" AS message + RETURN n.name AS person, "Older person" AS message } } RETURN person, message diff --git a/modules/ROOT/pages/queries/index.adoc b/modules/ROOT/pages/queries/index.adoc index 2e049d941..2fec764a1 100644 --- a/modules/ROOT/pages/queries/index.adoc +++ b/modules/ROOT/pages/queries/index.adoc @@ -2,7 +2,7 @@ :description: This page is an overview of the queries section in the Cypher Manual. This section provides a brief overview of the core concepts of a Cypher query (nodes, relationships, and paths), and examples of how to query a Neo4j graph database. -It also discusses composed queries; that is, how to use `UNION` or `WHEN...THEN...ELSE` operators to control the logic and execution of queries. +It also discusses how to compose combined queries using `UNION` and conditional queries using `WHEN`. * xref:queries/concepts.adoc[] * xref:queries/basic.adoc[] From 1f27b1cb52274a62903d60f91262b64bf4f47950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:09:22 +0100 Subject: [PATCH 15/32] syntax attempt --- .../composed-queries/conditional-queries.adoc | 153 +++++++++++++++--- 1 file changed, 133 insertions(+), 20 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 8a5e600b6..a3c74dcc6 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -33,13 +33,24 @@ CREATE ---- [[standalone-when-logic]] -== Standalone `WHEN` branches and basic logic +== Standalone `WHEN` branches + +.Syntax for standalone `WHEN` branches +[source, syntax] +---- +WHEN predicate THEN [{] + + [}] +[WHEN ...] +[ELSE [{] + +[}]] +---- -`WHEN` branches can be used independently of other clauses. The first 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 example demonstrates this logic: +The following examples demonstrates this logic: .`WHEN` logic [source, cypher] @@ -60,10 +71,44 @@ Since the second `WHEN` branch is `true`, it will execute, while the preceding b 1+d| Rows: 1 |=== +.Conditionally executing queries in standalone `WHEN` branches +[source, cypher] +---- +WHEN true THEN { + MATCH (n:Person) WHERE n.name STARTS WITH "A" + RETURN n.name AS name +} +ELSE { MATCH (n:Person) + RETURN n.name AS name +} +---- + +[NOTE] +The enclosing `{}` in the above example and the below examples are not required (unless explicitly stated), but they clarify the different conditional branches of the query. + +.Result +[role="queryresult",options="header,footer",cols="1*] + { + WHEN predicate THEN [{] + + [}] + [WHEN ...] + [ELSE [{] + + [}]] +} +[ ...] +[] +---- + .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`. @@ -138,10 +200,9 @@ Because only `Daniel` and `Eskil` had no outgoing `WORKS_FOR` relationships, the .Conditional `EXISTS` subquery ===== In this example, `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the condition (`n.age > 40`). -If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` is married to. - +If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` loves. [NOTE] -Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery is not available to the outer scope (the same is true for xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). +Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery are not available to the outer scope (the same is true for xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). .`WHEN` inside `EXISTS` subquery [source, cypher] @@ -162,8 +223,6 @@ RETURN n.name AS name, exists((n)-[:LOVES]->()) AS hasLovesRel ---- -Since each condition is satisfied by a distinct node, the query only returns two rows. - .Result [role="queryresult",options="header,footer",cols="3* + [}] +[WHEN ...] +[ELSE [{] + +[}]] +} +UNION [DISTINCT|ALL] +{ + WHEN predicate THEN [{] + + [}] +[WHEN ...] +[ELSE [{] + +[}]] +} +---- .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 + 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 + WHEN false THEN RETURN 4 AS x + WHEN false THEN RETURN 5 AS x + ELSE RETURN 6 AS x } ---- @@ -269,7 +352,37 @@ UNION 1+d| Rows: 2 |=== -If the queries combined by the `UNION` does not begin with a `WHEN` branch, then enclosing curly braces are not necessary (though they can still be used). +If the queries combined by the `UNION` does not begin with a `WHEN` branch, then enclosing curly braces are not necessary. + +.Syntax for combining conditional subqueries with `UNION` +[source, syntax] +---- +[] + { + WHEN predicate THEN [{] + + [}] + [WHEN ...] + [ELSE [{] + + [}]] +} +[ ...] +[] +UNION [DISTINCT|ALL] +[] + { + WHEN predicate THEN [{] + + [}] + [WHEN ...] + [ELSE [{] + + [}]] +} +[ ...] +[] +---- .Combining conditional logic with `UNION` ===== @@ -296,10 +409,10 @@ CALL (n) { UNION CALL (*) { WHEN n.age < 40 THEN { - RETURN n.name AS person, "Younger person" AS message + RETURN n.name AS person, "Younger person" AS message } ELSE { - RETURN n.name AS person, "Older person" AS message + RETURN n.name AS person, "Older person" AS message } } RETURN person, message From 9e6ec01ff2b08939b9758341fbeedd8f0bdfa341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:19:56 +0100 Subject: [PATCH 16/32] syntax update --- .../composed-queries/conditional-queries.adoc | 175 ++++++++---------- 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index a3c74dcc6..71cf68365 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -39,11 +39,11 @@ CREATE [source, syntax] ---- WHEN predicate THEN [{] - + [}] [WHEN ...] [ELSE [{] - + [}]] ---- @@ -96,7 +96,7 @@ The enclosing `{}` in the above example and the below examples are not required |=== -[[when-rules]] +[[conditional-rules]] == Rules Similar to `UNION`, the number and names of the columns must be identical in all parts of a `WHEN`/`ELSE` construct. @@ -139,30 +139,31 @@ RETURN n.name AS employees, f.name AS newManagerNode ---- -Instead, if `WHEN` constructs are part of a larger query, they must be either be placed within a subquery and/or on different sides of combined `UNION` queries. +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 chained xref:subqueries/index.adoc[subqueries] to execute a set of operations only when a specified condition evaluates to `true`. +`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 subqueries +.Syntax for conditional `CALL` subqueries [source, syntax] ---- -[] - { +[] + { WHEN predicate THEN [{] - + [}] - [WHEN ...] + [WHEN ...]* [ELSE [{] - + [}]] } -[ ...] -[] +[ ...]* +[] ---- -.Conditional `CALL` subquery +.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`. @@ -196,51 +197,10 @@ Because only `Daniel` and `Eskil` had no outgoing `WORKS_FOR` relationships, the |=== ===== - -.Conditional `EXISTS` subquery +.Chaining conditional `CALL` subqueries ===== -In this example, `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the condition (`n.age > 40`). -If the condition evaluates to `true`, the subquery fetches people that the `person` knows; otherwise, it retrieves who the `person` loves. -[NOTE] -Unlike `CALL` subqueries, variables returned in an `EXISTS` subquery are not available to the outer scope (the same is true for xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). -.`WHEN` inside `EXISTS` subquery -[source, cypher] ----- -MATCH (n:Person) -WHERE EXISTS { - WHEN n.age > 40 THEN { - MATCH (n)-[:WORKS_FOR]->(x:Person) - RETURN x - } - ELSE { - MATCH (n)-[:LOVES]->(x:Person) - RETURN x - } -} -RETURN n.name AS name, - n.age AS age, - exists((n)-[:LOVES]->()) AS hasLovesRel ----- - -.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 xref:subqueries/existential.adoc[`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 xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). + +.`WHEN` inside `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. + +.Result +[role="queryresult",options="header,footer",cols="2* + [}] -[WHEN ...] +[WHEN ...]* [ELSE [{] - + [}]] } UNION [DISTINCT|ALL] { WHEN predicate THEN [{] - + [}] -[WHEN ...] +[WHEN ...]* [ELSE [{] - + [}]] } +UNION [DISTINCT|ALL]* ---- .Combining conditional branches with `UNION` using `{}` @@ -354,35 +372,6 @@ UNION If the queries combined by the `UNION` does not begin with a `WHEN` branch, then enclosing curly braces are not necessary. -.Syntax for combining conditional subqueries with `UNION` -[source, syntax] ----- -[] - { - WHEN predicate THEN [{] - - [}] - [WHEN ...] - [ELSE [{] - - [}]] -} -[ ...] -[] -UNION [DISTINCT|ALL] -[] - { - WHEN predicate THEN [{] - - [}] - [WHEN ...] - [ELSE [{] - - [}]] -} -[ ...] -[] ----- .Combining conditional logic with `UNION` ===== From 6baa37fd662d6c9b6bb4488ed74de66dff9be1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:33:47 +0100 Subject: [PATCH 17/32] syntax is complicated --- .../composed-queries/conditional-queries.adoc | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 71cf68365..6f6447a97 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -41,7 +41,7 @@ CREATE WHEN predicate THEN [{] [}] -[WHEN ...] +[WHEN ...]* [ELSE [{] [}]] @@ -260,11 +260,11 @@ xref:subqueries/existential.adoc[`EXISTS`], xref:subqueries/collect.adoc[`COLLEC [] EXISTS|COUNT|COLLECT { WHEN predicate THEN [{] - + [}] [WHEN ...]* [ELSE [{] - + [}]] } [] @@ -273,12 +273,12 @@ EXISTS|COUNT|COLLECT { .Conditional `EXISTS` subquery ===== -In this example, `WHEN` is used inside an xref:subqueries/existential.adoc[`EXISTS` subquery] to conditionally execute different branches based on the evaluation of the predicate (`n.age > 40`). +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 xref:subqueries/count.adoc[`COUNT`] and xref:subqueries/collect.adoc[`COLLECT` subqueries]). +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 `EXISTS` subquery +.`WHEN` inside an `EXISTS` subquery [source, cypher] ---- MATCH (n:Person) @@ -300,7 +300,7 @@ RETURN n.name AS name, .Result [role="queryresult",options="header,footer",cols="2* + [}] -[WHEN ...]* -[ELSE [{] - + [WHEN ...]* + [ELSE [{] + [}]] } UNION [DISTINCT|ALL] { WHEN predicate THEN [{] - + [}] -[WHEN ...]* -[ELSE [{] - + [WHEN ...]* + [ELSE [{] + [}]] } -UNION [DISTINCT|ALL]* +[UNION [DISTINCT|ALL] ...]* ---- .Combining conditional branches with `UNION` using `{}` From b5ca1b2625045352b833a686af91e8b34339b621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:59:10 +0100 Subject: [PATCH 18/32] review corrections --- .../queries/composed-queries/conditional-queries.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index 6f6447a97..c7331032c 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -47,7 +47,7 @@ WHEN predicate THEN [{] [}]] ---- -The first predicate that evaluates to `true` will be executed. +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: @@ -57,10 +57,11 @@ The following examples demonstrates this logic: ---- 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 `ELSE` branch will be skipped. +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* Date: Mon, 3 Feb 2025 10:07:54 +0100 Subject: [PATCH 19/32] add count example --- .../composed-queries/conditional-queries.adoc | 4 +- .../ROOT/pages/subqueries/call-subquery.adoc | 2 +- modules/ROOT/pages/subqueries/collect.adoc | 6 ++- modules/ROOT/pages/subqueries/count.adoc | 40 +++++++++++++++++++ .../ROOT/pages/subqueries/existential.adoc | 2 +- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc index c7331032c..92ed9dfec 100644 --- a/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc +++ b/modules/ROOT/pages/queries/composed-queries/conditional-queries.adoc @@ -52,7 +52,7 @@ 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: -.`WHEN` logic +.Conditional logic [source, cypher] ---- WHEN false THEN RETURN 1 AS x @@ -247,7 +247,7 @@ RETURN n.name AS name, ageGroup, manager | "Charlie" | "Veteran" | [["Daniel", "Senior"]] | "Daniel" | "Senior" | [["Peter", "Senior"]] | "Eskil" | "Senior" | [["Peter", "Senior"]] -| "Peter" | "Senior" | [[NULL, NULL]] +| "Peter" | "Senior" | \[[NULL, NULL]] 3+d| Rows: 6 |=== diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc index 69497c20e..79d9d8a24 100644 --- a/modules/ROOT/pages/subqueries/call-subquery.adoc +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -488,7 +488,7 @@ Now all `Player` nodes, regardless of whether they have any `PLAYS_FOR` relation [[conditional-call]] == Conditional `CALL` subqueries -`WHEN` can be used inside `CALL` subqueries to execute branches only when a condition evaluates to `true`. +`WHEN` can be used inside `CALL` 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]. diff --git a/modules/ROOT/pages/subqueries/collect.adoc b/modules/ROOT/pages/subqueries/collect.adoc index 2a8362f55..ba4e86a09 100644 --- a/modules/ROOT/pages/subqueries/collect.adoc +++ b/modules/ROOT/pages/subqueries/collect.adoc @@ -74,9 +74,11 @@ RETURN person.name as name, COLLECT { 2+d|Rows: 3 |=== +[role=label--new-2025.02] +[[conditional-collect]] == Conditional `COLLECT` subquery -`WHEN` can be used inside `COLLECT` subqueries to execute branches only when a condition evaluates to `true`. +`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]. @@ -94,7 +96,7 @@ RETURN n.name AS name, ELSE { RETURN 'Cat owner' AS petStatus } -} 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]. From cc24b237fdb72191bc8a6655ff76f49234eb5e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:27:27 +0100 Subject: [PATCH 20/32] initital --- modules/ROOT/content-nav.adoc | 14 +- .../gql-conformance/supported-mandatory.adoc | 4 +- .../gql-conformance/supported-optional.adoc | 8 +- ...ions-additions-removals-compatibility.adoc | 6 +- .../expressions/conditional-expressions.adoc | 2 +- .../expressions/expressions-overview.adoc | 32 ++-- modules/ROOT/pages/expressions/index.adoc | 4 + .../predicate-expressions/index.adoc | 18 ++ .../predicate-expressions/operators.adoc | 178 ++++++++++++++++++ .../path-pattern-expressions.adoc | 1 + .../type-predicate-expressions.adoc} | 0 modules/ROOT/pages/functions/scalar.adoc | 2 +- modules/ROOT/pages/syntax/operators.adoc | 52 ----- .../ROOT/pages/values-and-types/index.adoc | 4 +- .../property-structural-constructed.adoc | 4 +- .../values-and-types/working-with-null.adoc | 2 +- 16 files changed, 244 insertions(+), 87 deletions(-) create mode 100644 modules/ROOT/pages/expressions/predicate-expressions/index.adoc create mode 100644 modules/ROOT/pages/expressions/predicate-expressions/operators.adoc create mode 100644 modules/ROOT/pages/expressions/predicate-expressions/path-pattern-expressions.adoc rename modules/ROOT/pages/{values-and-types/type-predicate.adoc => expressions/predicate-expressions/type-predicate-expressions.adoc} (100%) diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 2d08c7ecc..61e110324 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -37,10 +37,6 @@ ** 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[] @@ -65,7 +61,15 @@ ** xref:values-and-types/lists.adoc[] ** xref:values-and-types/maps.adoc[] ** xref:values-and-types/casting-data.adoc[] -** xref:values-and-types/type-predicate.adoc[] +** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] + +* xref:expressions/index.adoc[] +** xref:expressions/expressions-overview.adoc[] +** xref:expressions/predicate-expressions/index.adoc[] +*** xref:expressions/predicate-expressions/operators.adoc[] +*** xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] +*** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] +** xref:expressions/conditional-expressions.adoc[] * xref:functions/index.adoc[] ** xref:functions/aggregating.adoc[] diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index 3504f299e..1bdf406dc 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -143,12 +143,12 @@ This is currently not available in Cypher. | 19.5 | -| xref:values-and-types/type-predicate.adoc#type-predicate-null[Type predicate expressions for `NULL`] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-null[Type predicate expressions for `NULL`] | | 19.6 | -| xref:values-and-types/type-predicate.adoc#[] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#[] | | 19.7 diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc index 30333330c..cefd8640e 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -85,7 +85,7 @@ These codes order the features in the table below. | GA06 | Value type predicates -| xref:values-and-types/type-predicate.adoc[Type predicate expressions] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc[Type predicate expressions] | | GA07 @@ -199,12 +199,12 @@ GQL also defines a parameterless version of the function not in Cypher: `CURRENT | GV66 | Open dynamic unions -| xref:values-and-types/type-predicate.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] | | GV67 | Closed dynamic unions -| xref:values-and-types/type-predicate.adoc#type-predicate-closed-dynamic-unions[Closed dynamic unions] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-closed-dynamic-unions[Closed dynamic unions] | | GV70 @@ -214,7 +214,7 @@ GQL also defines a parameterless version of the function not in Cypher: `CURRENT | GV71 | Immaterial value types: empty type support (`NOTHING`)] -| xref:values-and-types/type-predicate.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] +| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] | |=== diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 1188bfc65..7553f67da 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -1687,7 +1687,7 @@ IS [NOT] :: ---- a| -Extended xref:values-and-types/type-predicate.adoc[type predicate expressions]. +Extended xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. Closed dynamic union types (`type1 \| type2 \| ...`) are now supported. For example, the following query which evaluates to true if a value is either of type `INTEGER` or `FLOAT`: [source, cypher, role="noheader"] @@ -1746,7 +1746,7 @@ IS [NOT] :: ---- a| -Extended xref:values-and-types/type-predicate.adoc[type predicate expressions]. +Extended xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. The newly supported types are: * `NOTHING` @@ -1920,7 +1920,7 @@ IS [NOT] :: ---- a| -Added xref:values-and-types/type-predicate.adoc[type predicate expressions]. +Added xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. The available types are: * `BOOLEAN` diff --git a/modules/ROOT/pages/expressions/conditional-expressions.adoc b/modules/ROOT/pages/expressions/conditional-expressions.adoc index 9c6d93c21..9d0bae2da 100644 --- a/modules/ROOT/pages/expressions/conditional-expressions.adoc +++ b/modules/ROOT/pages/expressions/conditional-expressions.adoc @@ -108,7 +108,7 @@ The supported comparators are: * xref::syntax/operators.adoc#query-operators-comparison[Regular Comparison Operators]: `+=+`, `+<>+`, `+<+`, `+>+`, `+<=+`, `+>=+` * xref:values-and-types/working-with-null.adoc#is-null-is-not-null[`IS NULL` Operator]: `IS [NOT] NULL` -* xref:values-and-types/type-predicate.adoc[Type Predicate Expression]: `IS [NOT] TYPED ` (Note that the form `IS [NOT] :: ` is not accepted) +* xref:expressions/predicate-expressions/type-predicate-expressions.adoc[Type Predicate Expression]: `IS [NOT] TYPED ` (Note that the form `IS [NOT] :: ` is not accepted) * xref::syntax/operators.adoc#match-string-is-normalized[Normalization Predicate Expression]: `IS [NOT] NORMALIZED` * xref::syntax/operators.adoc#query-operator-comparison-string-specific[String Comparison Operators]: `STARTS WITH`, `ENDS WITH`, `=~` (regex matching) diff --git a/modules/ROOT/pages/expressions/expressions-overview.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc index e45863e9f..9e6ad2cf0 100644 --- a/modules/ROOT/pages/expressions/expressions-overview.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -1,4 +1,4 @@ -= Overview += Expressions overview :description: Overview of the expressions allowed in Cypher. This page contains an overview of the allowed expressions in Cypher. @@ -6,19 +6,21 @@ This page contains an overview of the allowed expressions in Cypher. [[general]] == General -* A variable: `n`, `x`, `rel`, `myFancyVariable`, `++`A name with special characters in it[]!`++`. -* A property: `n.prop`, `x.prop`, `rel.thisProperty`, `++myFancyVariable.`(special property name)`++`. -* A dynamic property: `n["prop"]`, `rel[n.city + n.zip]`, `map[coll[0]]`. -* A parameter: `$param`, `$0`. -* A list of expressions: `['a', 'b']`, `[1, 2, 3]`, `['a', 2, n.property, $param]`, `[]`. -* A function call: `length(p)`, `nodes(p)`. -* An aggregate function call: `avg(x.prop)`, `+count(*)+`. -* A path-pattern: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. -* An operator application: `1 + 2`, `3 < 4`. -* A subquery expression: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. -* A regular expression: `a.name =~ 'Tim.*'`. -* A `CASE` expression. -* `null`. +* A xref:syntax/variables.adoc[variable]: `n`, `x`, `rel`, `myFancyVariable`, `++`A name with special characters in it[]!`++`. +* A xref:queries/concepts.adoc[property]: `n.prop`, `x.prop`, `rel.thisProperty`, `++myFancyVariable.`(special property name)`++`. +For more information, see xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]. +* A xref:clauses/set.adoc#dynamic-set-property[dynamic property]: `n["prop"]`, `rel[n.city + n.zip]`, `map[coll[0]]`. +For more information, see xref:clauses/where.adoc#filter-on-dynamic-property[`WHERE` -> filter on dynamically computed properties]. +* A xref:syntax/parameters.adoc[parameter]: `$param`, `$0`. +* A xref:values-and-types/lists.adoc[list of expressions]: `['a', 'b']`, `[1, 2, 3]`, `['a', 2, n.property, $param]`, `[]`. +* A xref:functions/index.adoc[function] call: `length(p)`, `nodes(p)`. +* An xref:functions/aggregating.adoc[aggregating function] call: `avg(x.prop)`, `+count(*)+`. +* A xref:patterns/fixed-length-patterns.adoc#path-patterns[path-pattern]: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. +* An xref:syntax/operators.adoc[operator application]: `1 + 2`, `3 < 4`. +* A xref:subqueries/index.adoc[subquery expression]: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. +* A xref:clauses/where.adoc#query-where-regex[regular expression]: `a.name =~ 'Tim.*'`. +* A xref:expressions/conditional-expressions.adoc[`CASE` expression]. +* xref:values-and-types/working-with-null[`null`]. [NOTE] ==== @@ -30,7 +32,7 @@ Learn more in link:https://neo4j.com/developer/kb/protecting-against-cypher-inje [NOTE] ==== Most expressions in Cypher evaluate to `null` if any of their inner expressions are `null`. -Notable exceptions are the operators `IS NULL`, `IS NOT NULL`, and the xref:values-and-types/type-predicate.adoc[type predicate expressions]. +Notable exceptions are the operators `IS NULL`, `IS NOT NULL`, and the xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. ==== [[numerical]] diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index 144420147..4f6834b1e 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -6,4 +6,8 @@ Expressions are used to perform operations like calculations, comparisons, and d This section includes: * xref:expressions/expressions-overview.adoc[] +* xref:expressions/predicate-expressions/index.adoc[] +** xref:expressions/predicate-expressions/operators.adoc[] +** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] +** xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc new file mode 100644 index 000000000..3637260b6 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc @@ -0,0 +1,18 @@ += Predicate expressions + +A predicate is a `BOOLEAN` expression that evaluates to `TRUE`, `FALSE`, or `NULL` +Predicate expressions are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. + +This chapter is divided into the following sections: + +* xref:expressions/predicate-expressions/operators.adoc[] - information about `BOOLEAN`, comparison, `STRING`, and `LIST` operators. +* xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] - Information about filtering queries with path pattern expressions. +* xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] - Information about how to verify the value type of a Cypher expression. + +The following can also serve as a predicate expressions (but are documented elsewhere): + +* xref:syntax/variables.adoc[] +* xref:queries/concepts.adoc[Properties] (See also xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]) +* xref:functions/index.adoc[] returning a `BOOLEAN`. +For more information, see xref:functions/predicate.adoc[Predicate functions]. +* xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc new file mode 100644 index 000000000..76ce1fe22 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -0,0 +1,178 @@ += Predicate operators + +[[where-example-graph]] +== Example graph + +The following graph is used for the examples below: + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE + (alice:Person {name:'Alice', age: 65, email: 'alice@example.com', occupation: 'Project manager'}), + (bob:Person {name: 'Bob', age: 25, email: 'bob@example.com', occupation: 'Software developer'}), + (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@example.com', occupation: 'Director'}), + (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@example.com}), + (bob)-[:WORKS_FOR]->(alice), + (cecilia)-[:WORKS_FOR]->(alice), + (alice)-[:WORKS_FOR]->(daniel), + (charlie)-[:WORKS_FOR]->(daniel), + (bob)-[:LOVES]->(eskil), + (charlie)-[:LOVES]->(alice) +---- + +[[boolean-operators]] +== Boolean operators + +The boolean operators comprise: + +* conjunction: `AND` +* disjunction: `OR` +* exclusive disjunction: `XOR` +* negation: `NOT` + +.Truth table for boolean operators +[options="header", cols="^,^,^,^,^,^", width="85%"] +|=== +|a | b | a `AND` b | a `OR` b | a `XOR` b | `NOT` a +|`false` | `false` | `false` | `false` | `false` | `true` +|`false` | `null` | `false` | `null` | `null` | `true` +|`false` | `true` | `false` | `true` | `true` | `true` +|`true` | `false` | `false` | `true` | `true` | `false` +|`true` | `null` | `null` | `true` | `null` | `false` +|`true` | `true` | `true` | `true` | `false` | `false` +|`null` | `false` | `false` | `null` | `null` | `null` +|`null` | `null` | `null` | `null` | `null` | `null` +|`null` | `true` | `null` | `true` | `null` | `null` +|=== + +.Boolean operators +===== + +.`AND` operator +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age > 30 AND n.occupation = 'Software developer' +RETURN n.name AS name, n.age AS age, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 30 XOR n.occupation = 'Software developer' +RETURN n.name AS name, n.age AS age, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 30 AND NOT n.occupation = 'Software developer' +RETURN n.name AS name, n.age AS age, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 60 AND n.occupation = 'Security engineer') OR NOT (n.occupation = 'Director' OR n.name = 'Eskil') +RETURN n.name AS name, n.age AS age, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="2* 6 AND number < 10) +RETURN number +---- + +.Result +[role="queryresult",options="header,footer",cols="1*+`, `+<+`, `+>+`, `+<=+`, `+>=+`, `IS NULL`, `IS NOT NULL` | xref::syntax/operators.adoc#query-operators-comparison[`STRING`-specific comparison operators] | `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `=~` (regex matching) -| xref::syntax/operators.adoc#query-operators-boolean[Boolean operators] | `AND`, `OR`, `XOR`, `NOT` | xref::syntax/operators.adoc#query-operators-string[String operators] | `+` and `\|\|` (string concatenation), `IS NORMALIZED` | xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] | `+` and `-` for operations between durations and temporal instants/durations, `*` and `/` for operations between durations and numbers | xref::syntax/operators.adoc#query-operators-map[Map operators] | `.` for static value access by key, `[]` for dynamic value access by key @@ -477,57 +476,6 @@ RETURN word Further information and examples regarding the use of regular expressions in filtering can be found in xref::clauses/where.adoc#query-where-regex[Regular expressions]. -[[query-operators-boolean]] -== Boolean operators - -The boolean operators -- also known as logical operators -- comprise: - -* conjunction: `AND` -* disjunction: `OR`, -* exclusive disjunction: `XOR` -* negation: `NOT` - -Here is the truth table for `AND`, `OR`, `XOR` and `NOT`. - -[options="header", cols="^,^,^,^,^,^", width="85%"] -|=== -|a | b | a `AND` b | a `OR` b | a `XOR` b | `NOT` a -|`false` | `false` | `false` | `false` | `false` | `true` -|`false` | `null` | `false` | `null` | `null` | `true` -|`false` | `true` | `false` | `true` | `true` | `true` -|`true` | `false` | `false` | `true` | `true` | `false` -|`true` | `null` | `null` | `true` | `null` | `false` -|`true` | `true` | `true` | `true` | `false` | `false` -|`null` | `false` | `false` | `null` | `null` | `null` -|`null` | `null` | `null` | `null` | `null` | `null` -|`null` | `true` | `null` | `true` | `null` | `null` -|=== - - -[[syntax-using-boolean-operators-to-filter-numbers]] -=== Using boolean operators to filter numbers - -.Query -[source, cypher] ----- -WITH [2, 4, 7, 9, 12] AS numberlist -UNWIND numberlist AS number -WITH number -WHERE number = 4 OR (number > 6 AND number < 10) -RETURN number ----- - -.Result -[role="queryresult",options="header,footer",cols="1* | BOOLEAN | LIST`, the normalized type would be: `BOOLEAN | LIST`. -This normalization is run on types used in xref::values-and-types/type-predicate.adoc[type predicate expressions], and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. +This normalization is run on types used in xref::expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions], and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. Type normalization is also used to ensure the consistency of the output for the xref::functions/scalar.adoc#functions-valueType[valueType()] function. [[ordering-of-types]] diff --git a/modules/ROOT/pages/values-and-types/working-with-null.adoc b/modules/ROOT/pages/values-and-types/working-with-null.adoc index 7c96a40fa..df7a68920 100644 --- a/modules/ROOT/pages/values-and-types/working-with-null.adoc +++ b/modules/ROOT/pages/values-and-types/working-with-null.adoc @@ -5,7 +5,7 @@ In Cypher, `null` is used to represent missing or undefined values. All data types in Cypher are nullable. -This means that xref::values-and-types/type-predicate.adoc#type-predicate-null[type predicate expressions] always return `true` for `null` values. +This means that xref::expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-null[type predicate expressions] always return `true` for `null` values. Conceptually, `null` means **a missing or unknown value**, and it is treated somewhat differently from other values. For example, returning a property from a node that does not have said property produces `null`. From 21145be175ea04dcd9d479ff29e103359ba3e67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:54:55 +0100 Subject: [PATCH 21/32] equality and order --- modules/ROOT/pages/clauses/where.adoc | 26 -- .../predicate-expressions/index.adoc | 2 +- .../predicate-expressions/operators.adoc | 383 +++++++++++++++++- 3 files changed, 365 insertions(+), 46 deletions(-) diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index 87abb66bb..833321040 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -80,32 +80,6 @@ RETURN [(a)-->(b WHERE b:Person) | b.name] AS friends |Rows: 1 |=== -[[boolean-operations]] -=== Boolean operations - -The following boolean operators can be used with the `WHERE` clause: `AND`, `OR`, `XOR`, and `NOT`. -For more information on how operators work with `null`, see the chapter on xref::values-and-types/working-with-null.adoc[Working with null]. - -.Query -[source, cypher] ----- -MATCH (n:Person) -WHERE n.name = 'Peter' XOR (n.age < 30 AND n.name = 'Timothy') OR NOT (n.name = 'Timothy' OR n.name = 'Peter') -RETURN - n.name AS name, - n.age AS age -ORDER BY name ----- - -.Result -[role="queryresult",options="header,footer",cols="2*`, `<`, `>`, `<=`, `>=`, `IS NULL`, `IS NOT NULL`) +* xref:expressions/predicate-expressions/operators.adoc#string-operators[String operators] (`=~`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`) +* xref:expressions/predicate-expressions/operators.adoc#list-operators[List operators] (`IN`) [[where-example-graph]] == Example graph @@ -15,7 +24,7 @@ CREATE (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@example.com', occupation: 'Director'}), - (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@example.com}), + (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@example.com'}), (bob)-[:WORKS_FOR]->(alice), (cecilia)-[:WORKS_FOR]->(alice), (alice)-[:WORKS_FOR]->(daniel), @@ -29,10 +38,10 @@ CREATE The boolean operators comprise: -* conjunction: `AND` -* disjunction: `OR` -* exclusive disjunction: `XOR` -* negation: `NOT` +* Conjunction: `AND` +* Disjunction: `OR` +* Exclusive disjunction: `XOR` +* Negation: `NOT` .Truth table for boolean operators [options="header", cols="^,^,^,^,^,^", width="85%"] @@ -65,7 +74,7 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation |=== | name | age | occupation -| "Cecilia" | 31 | "Software developer" +| "Cecilia" | 31 | "Software developer" 3+d|Rows: 1 |=== @@ -102,10 +111,10 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation |=== | name | age | occupation -| "Alice" | 65 | "Project manager" -| "Bob" | 25 | "Software developer" -| "Charlie" | 61 | "Security engineer" -| "Daniel" | 39 | "Director" +| "Alice" | 65 | "Project manager" +| "Bob" | 25 | "Software developer" +| "Charlie" | 61 | "Security engineer" +| "Daniel" | 39 | "Director" 3+d|Rows: 4 |=== @@ -123,9 +132,9 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation |=== | name | age | occupation -| "Alice" | 65 | "Project manager" -| "Charlie" | 61 | "Security engineer" -| "Daniel" | 39 | "Director" +| "Alice" | 65 | "Project manager" +| "Charlie" | 61 | "Security engineer" +| "Daniel" | 39 | "Director" 3+d|Rows: 3 |=== @@ -133,19 +142,19 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation .Combining boolean operators [source, cypher] ---- - MATCH (n:Person) +MATCH (n:Person) WHERE n.occupation = 'Software developer' XOR (n.age > 60 AND n.occupation = 'Security engineer') OR NOT (n.occupation = 'Director' OR n.name = 'Eskil') RETURN n.name AS name, n.age AS age, n.occupation AS occupation ---- .Result -[role="queryresult",options="header,footer",cols="2*` +* Less than: `<` +* Greater than: `>` +* Less than or equal to: `\<=` +* Greater than or equal to: `>=` +* `IS NULL` +* `IS NOT NULL` + +[TIP] +For more information about `null`, see xref:values-and-types/working-with-null.adoc[]. + +.Comparison operators +===== + +.Equality operator (`=`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.occupation = 'Software developer' +RETURN n.name AS name, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.occupation <> 'Software developer' +RETURN n.name AS name, n.occupation AS occupation +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age > 39 +RETURN n.name AS name, n.age AS age +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age > 39 +RETURN n.name AS name, n.age AS age +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) operators allows for comparing the equality of different values. + +Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). + +Maps are only equal if they map exactly the same keys to equal values and lists are only equal if they contain the same sequence of equal values (e.g. `[3, 4] = [1+2, 8/2]`). + +Values of different types are considered as equal according to the following rules: + +* `PATH` values are treated as lists of alternating nodes and relationships and are equal to all lists that contain an identical sequence of nodes and relationships. +* Testing any value against `null` with either the `=` or `<>` operator always evaluates to `null`. +This includes `null = null` and `null <> null`. +To reliably test if a value is `null` use `v IS NULL` or `v IS NOT NULL` (the latter is equivalent to `NOT(v IS NULL)`). + +Other combinations of value types cannot be compared with each other. +For example, nodes, relationships, and literal maps cannot be compared to one another. +Comparing incomparable values will throw an error. + +[[ordering-and-comparison]] +=== Ordering and comparison of values + +xref:clauses/order-by.adoc[`ORDER BY`] requires that all values are orderable. +The following points explain how comparisons are made when using the `\<=`, `<`,`>=`, `>` operators. + +* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is `true`). +* All comparability tests with `java.lang.Double.NaN` evaluate as `false`. +For example, `1 > b` and `1 < b` are both `false` when `b` is NaN. +* String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). +* Boolean values are compared for ordering such that `false < true`. +* When comparing values for ordering, if one of the arguments is `null`, the result is always `null`. +* xref:values-and-types/spatial.adoc[Spatial values] cannot be compared using the operators `\<=`, `<`,`>=`, `>`. +To compare spatial values within a specific range, use either the xref:functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] or the xref:functions/spatial.adoc#functions-point-wgs84-2d[`point()`] function. + +This section also includes the following categories: + +* xref:expressions/predicate-expressions/operators.adoc#value-hierarchy[Hierarchy of values] +* xref:expressions/predicate-expressions/operators.adoc#ordering-lists-maps[Ordering list and map values] +* xref:expressions/predicate-expressions/operators.adoc#ordering-spatial-temporal[Ordering spatial and temporal values] + +[[value-hierarchy]] +==== Hierarchy of values + +Values of different types are ordered based on a predefined hierarchy, from least to greatest, as outlined in the following list: + +* xref::values-and-types/maps.adoc#cypher-literal-maps[`MAP`] +* xref::values-and-types/property-structural-constructed.adoc#structural-types[`NODE`] +* xref::values-and-types/property-structural-constructed.adoc#structural-types[`RELATIONSHIP`] +* xref::values-and-types/lists.adoc[`LIST`] +* xref::patterns/fixed-length-patterns.adoc#path-patterns[`PATH`] +* xref::values-and-types/temporal.adoc[`ZONED DATETIME`] +* xref::values-and-types/temporal.adoc[`LOCAL DATETIME`] +* xref::values-and-types/temporal.adoc[`DATE`] +* xref::values-and-types/temporal.adoc[`ZONED TIME`] +* xref::values-and-types/temporal.adoc[`LOCAL TIME`] +* xref::values-and-types/temporal.adoc[`DURATION`] +* xref::values-and-types/spatial.adoc[`POINT`] +* xref::expressions/expressions-overview.adoc#string[`STRING`] +* xref::expressions/expressions-overview.adoc#boolean[`BOOLEAN`] +* Numbers: xref::expressions/expressions-overview.adoc#numerical[`INTEGER`, `FLOAT`] + +[NOTE] +`null` is ordered after all other values. + +.Sorting rules for mixed Cypher types +[source, cypher] +---- +WITH [42, "hello", null, true, {name: "Alice"}, [1, 2, 3], date("2024-02-10")] AS v +UNWIND v AS values +RETURN values +ORDER BY values +---- + +.Result +[role="queryresult",options="header,footer",cols="1* Constructed types]. + +[[ordering-spatial-temporal]] +==== Ordering spatial and temporal values + +The following applies to ordering of xref:values-and-types/spatial.adoc[spatial types]: + +* `POINT` values are ordered after lists and before temporal types. +* `POINT` values of different coordinate reference systems (CRS) are ordered by the CRS code (the value of SRID field). +For the currently supported set of xref::values-and-types/spatial.adoc#cypher-spatial-crs[CRS], the following ascending order applies: `4326`, `4979`, `7302`, `9157`. +* `POINT` values with the same CRS are ordered by each coordinate value in turn; first `x`, then `y`, and finally `z`. +* Note that this ordering is different to the order returned by the spatial index, which follows the space filling curve. + +The following applies to the ordering of xref:values-and-types/temporal.adoc[temporal types]: + +* Temporal types are ordered after spatial types but before strings. +* Temporal values follow a chronological order. +For example, `2023-01-01` comes before `2024-01-01`. +* Temporal values are first sorted by type, then by value. +For example, `DATETIME` is considered "greater" than a `DATE` and `2023-02-10T12:00:00` comes before `2023-02-10T15:00:00` because it is chronologically earlier. +* Since there is no perfect way to compare duration values (because months and years have varying lengths), Cypher defines a specific rule for sorting them in `ORDER BY`: +** 1 year is treated as 365.2425 days (to account for leap years). +** 1 month is treated as 30.436875 days (which is 1/12 of a year). +** 1 day is always 24 hours. + +The following applies to comparing temporal types: + +* xref::values-and-types/temporal.adoc#cypher-temporal-instants[Temporal instant values] (like `DATETIME` and `DATE`) can be compared if they are of the same type. +An earlier instant is considered smaller (less than) compared to a later instant. +* Instants at the same point in time but with different time zones are not considered equal. +To ensure consistent ordering, Cypher sorts them first by their actual point in time. +If two instants have the same time but different time zones, they are ordered by their UTC offset (west to east, meaning negative offsets come first). +If they have the same time and offset but different named time zones, they are sorted alphabetically by the time zone name. +* Duration values cannot be directly compared. +Since the length of a day, month, or year varies, Cypher does not define a strict ordering for durations. +Compare two durations `(e.g, duration1 < duration2)`, the result will always be `null`. + +[[string-operators]] +== String operators + +[[list-operators]] +== List operators From 214285135d9a8893fa27e9963379ac5b1069c61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Fri, 14 Feb 2025 11:04:22 +0100 Subject: [PATCH 22/32] restructuring --- modules/ROOT/pages/clauses/where.adoc | 578 +----------------- .../predicate-expressions/operators.adoc | 368 +++++++++-- .../path-pattern-expressions.adoc | 75 ++- modules/ROOT/pages/syntax/operators.adoc | 93 --- 4 files changed, 434 insertions(+), 680 deletions(-) diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index 833321040..04f116ac5 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -6,7 +6,7 @@ [[where-introduction]] == Introduction -The `WHERE` clause is not a clause in its own right -- rather, it is part of the `MATCH`, `OPTIONAL MATCH`, and `WITH` clauses. +The `WHERE` clause is not a clause in its own right -- rather, it is subclause used with `MATCH`, `OPTIONAL MATCH`, and `WITH` clauses. When used with `MATCH` and `OPTIONAL MATCH`, `WHERE` adds constraints to the patterns described. _It should not be seen as a filter after the matching is finished._ @@ -37,57 +37,12 @@ CREATE (andy)-[:KNOWS {since: 1999}]->(peter) ---- -[[query-where-basic]] -== Basic usage - -[[node-pattern-predicates]] -=== Node pattern predicates - -`WHERE` can appear inside a node pattern in a `MATCH` clause or a pattern comprehension: - -.Query -[source, cypher] ----- -WITH 30 AS minAge -MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(b:Person WHERE b.age > minAge) -RETURN b.name ----- - -.Result -[role="queryresult",options="header,footer",cols="1*(b WHERE b:Person) | b.name] AS friends ----- - -.Result -[role="queryresult",options="header,footer",cols="1*(f) @@ -155,7 +103,7 @@ The `name`, `age` and `email` values for `Peter` are returned because `Andy` has [[filter-on-dynamic-property]] -=== Filter on dynamically-computed node property +== Filter on dynamic properties To filter on a property using a dynamically computed name, use square bracket syntax: @@ -167,7 +115,7 @@ To filter on a property using a dynamically computed name, use square bracket sy } ---- -.Query +.Filter on dynamically computed node property [source, cypher] ---- MATCH (n:Person) @@ -186,32 +134,8 @@ The `name` and `age` values for `Timothy` are returned because he is less than 3 |=== -[[property-existence-checking]] -=== Property existence checking - -Use the `IS NOT NULL` predicate to only include nodes or relationships in which a property exists: - -.Query -[source, cypher] ----- -MATCH (n:Person) -WHERE n.belt IS NOT NULL -RETURN n.name, n.belt ----- - -The `name` and `belt` values for `Andy` are returned because he is the only one with a `belt` property: - -.Result -[role="queryresult",options="header,footer",cols="2*(timothy) -RETURN other.name, other.age ----- - -The `name` and `age` values for nodes that have an outgoing relationship to `Timothy` are returned: - -.Result -[role="queryresult",options="header,footer",cols="2*(peter) -RETURN other.name, other.age ----- - -The `name` and `age` values for nodes that do not have an outgoing relationship to `Peter` are returned: - -.Result -[role="queryresult",options="header,footer",cols="2* -3+|Rows: 1 -|=== - - -[[query-where-ranges]] -== Using ranges +[[pattern-element-predicates]] +== Filter fixed-length patterns -[[simple-range]] -=== Simple range +`WHERE` clauses can be added to pattern elements in order to specify additional constraints. -To check whether an element exists within a specific range, use the inequality operators `<`, `<=`, `>=`, `>`: +`WHERE` can appear inside a node pattern in a `MATCH` clause or a pattern comprehension: -.Query +.Using `WHERE` inside a node pattern [source, cypher] ---- -MATCH (a:Person) -WHERE a.name >= 'Peter' -RETURN a.name, a.age +WITH 30 AS minAge +MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(b:Person WHERE b.age > minAge) +RETURN b.name ---- -The `name` and `age` values of nodes having a `name` property lexicographically (i.e. using the dictionary order) greater than or equal to `Peter` are returned: - .Result -[role="queryresult",options="header,footer",cols="2* 'Andy' AND a.name < 'Timothy' -RETURN a.name, a.age +MATCH (a:Person {name: 'Andy'}) +RETURN [(a)-->(b WHERE b:Person) | b.name] AS friends ---- -The `name` and `age` values of nodes having a `name` property lexicographically between `Andy` and `Timothy` are returned: - .Result -[role="queryresult",options="header,footer",cols="2* b.yearOfBirth]->(b:Person) -RETURN r.since ----- - -.Error message -[source, output] ----- -Relationship pattern predicates are not supported for variable-length relationships. ----- - - Putting predicates inside a relationship pattern can help with readability. Note that it is strictly equivalent to using a standalone `WHERE` sub-clause. @@ -824,4 +308,4 @@ RETURN [(a)-[r:KNOWS WHERE r.since < minYear]->(b:Person) | r.since] AS years |Rows: 1 |=== - +For information about using `WHERE` in variable-length patterns, see xref:patterns/variable-length-patterns.adoc#quantified-path-patterns-predicates[] \ No newline at end of file diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc index b75368b4a..0076d246e 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -1,7 +1,7 @@ = Predicate operators :table-caption!: -Predicate operators define conditions to filter and retrieve data in queries. +Predicate operators are used to evaluate expressions and return `BOOLEAN` values. They are categorized as follows: * xref:expressions/predicate-expressions/operators.adoc#boolean-operators[Boolean operators] (`AND`, `OR`, `XOR`, `NOT`) @@ -19,13 +19,13 @@ To recreate the graph, run the following query in an empty Neo4j database: [source, cypher, role=test-setup] ---- CREATE - (alice:Person {name:'Alice', age: 65, email: 'alice@example.com', occupation: 'Project manager'}), - (bob:Person {name: 'Bob', age: 25, email: 'bob@example.com', occupation: 'Software developer'}), + (alice:Person {name:'Alice', age: 65, email: 'alice@company.com', occupation: 'Project manager'}), + (cecil:Person {name: 'Cecil', age: 25, email: 'bob@company.com', occupation: 'Software developer'}), (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), - (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@example.com', occupation: 'Director'}), - (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@example.com'}), - (bob)-[:WORKS_FOR]->(alice), + (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@company.com', occupation: 'Director'}), + (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@private.se'}), + (cecil)-[:WORKS_FOR]->(alice), (cecilia)-[:WORKS_FOR]->(alice), (alice)-[:WORKS_FOR]->(daniel), (charlie)-[:WORKS_FOR]->(daniel), @@ -36,7 +36,7 @@ CREATE [[boolean-operators]] == Boolean operators -The boolean operators comprise: +The predicate boolean operators comprise: * Conjunction: `AND` * Disjunction: `OR` @@ -93,7 +93,7 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation | name | age | occupation | "Cecilia" | 31 | "Software developer" -| "Bob" | 25 | "Software developer" +| "Cecil" | 25 | "Software developer" 3+d|Rows: 2 |=== @@ -112,7 +112,7 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation | name | age | occupation | "Alice" | 65 | "Project manager" -| "Bob" | 25 | "Software developer" +| "Cecil" | 25 | "Software developer" | "Charlie" | 61 | "Security engineer" | "Daniel" | 39 | "Director" @@ -160,34 +160,12 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation |=== - -.Filtering numbers with boolean operators -[source, cypher] ----- -WITH [2, 4, 7, 9, 12] AS numberlist -UNWIND numberlist AS number -WITH number -WHERE number = 4 OR (number > 6 AND number < 10) -RETURN number ----- - -.Result -[role="queryresult",options="header,footer",cols="1*` @@ -217,7 +195,7 @@ RETURN n.name AS name, n.occupation AS occupation |=== | name | occupation -| "Bob" | "Software developer" +| "Cecil" | "Software developer" | "Cecilia" | "Software developer" 2+d|Rows: 2 @@ -256,7 +234,7 @@ RETURN n.name AS name, n.age AS age |=== | name | age -| "Bob" | 25 +| "Cecil" | 25 | "Cecilia" | 31 2+d|Rows: 2 @@ -275,7 +253,7 @@ RETURN n.name AS name, n.age AS age |=== | name | age -| "Bob" | 25 +| "Cecil" | 25 | "Cecilia" | 31 | "Daniel" | 39 | "Eskil" | 39 @@ -356,10 +334,10 @@ RETURN n.name AS name |=== | name | email -| "Alice" | "alice@example.com" -| "Bob" | "bob@example.com" -| "Daniel" | "daniel@example.com" -| "Eskil" | "eskil@example.com" +| "Alice" | "alice@company.com" +| "Cecil" | "bob@company.com" +| "Daniel" | "daniel@company.com" +| "Eskil" | "eskil@private.se" 2+d|Rows: 4 @@ -518,6 +496,318 @@ Compare two durations `(e.g, duration1 < duration2)`, the result will always be [[string-operators]] == String operators +The predicate string operators comprise: + +* Prefix: `STARTS WITH` (case sensitive) +* Suffix: `ENDS WITH` (case sensitive) +* substring: `CONTAINS` (case sensitive) +* Regular expression: `=~` +* `IS NORMALIZED` +* `IS NOT NORMALIZED` + +These operators perform _case-sensitive_ matching. +Attempting to use them operators on values which are not `STRING` values will return `null`. + +.Prefix, suffix, and substring operators +===== + +.`STARTS WITH` operator +[source, cypher] +---- +MATCH (n:Person) +WHERE n.name STARTS WITH 'C' +RETURN n.name AS name +---- + + +.Result +[role="queryresult",options="header,footer",cols="1*(timothy) +RETURN other.name, other.age +---- + +The `name` and `age` values for nodes that have an outgoing relationship to `Timothy` are returned: + +.Result +[role="queryresult",options="header,footer",cols="2*(peter) +RETURN other.name, other.age +---- + +The `name` and `age` values for nodes that do not have an outgoing relationship to `Peter` are returned: + +.Result +[role="queryresult",options="header,footer",cols="2* Date: Fri, 14 Feb 2025 16:03:20 +0100 Subject: [PATCH 23/32] rewrite where --- modules/ROOT/pages/clauses/where.adoc | 249 ++++++++++++-------------- 1 file changed, 119 insertions(+), 130 deletions(-) diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index 04f116ac5..f8ff31db1 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -1,22 +1,21 @@ :description: `WHERE` adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. +:table-caption!: [[query-where]] = WHERE -[[where-introduction]] -== Introduction - -The `WHERE` clause is not a clause in its own right -- rather, it is subclause used with `MATCH`, `OPTIONAL MATCH`, and `WITH` clauses. +The `WHERE` clause is not a clause in its own right -- rather, it is subclause used with xref:clauses/match.adoc[`MATCH`], xref:clauses/optional-match.adoc[`OPTIONAL MATCH`], and xref:clauses/with.adoc[`WITH`] clauses. When used with `MATCH` and `OPTIONAL MATCH`, `WHERE` adds constraints to the patterns described. _It should not be seen as a filter after the matching is finished._ -In the case of `WITH`, however, `WHERE` simply filters the results. - In the case of multiple `MATCH` / `OPTIONAL MATCH` clauses, the predicate in `WHERE` is always a part of the patterns in the directly preceding `MATCH` / `OPTIONAL MATCH`. Both results and performance may be impacted if `WHERE` is put inside the wrong `MATCH` clause. -xref:indexes/search-performance-indexes/managing-indexes.adoc[Indexes] may be used to optimize queries using `WHERE` in a variety of cases. +When used after `WITH`, `WHERE` simply filters the results. + +[TIP] +For more uses of `WHERE`, see xref:expressions/predicate-expressions/index.adoc[]. [[where-example-graph]] == Example graph @@ -29,83 +28,84 @@ To recreate the graph, run the following query in an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE -(andy:Swedish:Person {name: 'Andy', age: 36, belt: 'white'}), -(timothy:Person {name: 'Timothy', age: 25}), -(peter:Person {name: 'Peter', age: 35, email: 'peter_n@example.com'}), -(andy)-[:KNOWS {since: 2012}]->(timothy), -(andy)-[:KNOWS {since: 1999}]->(peter) +CREATE (andy:Swedish:Person {name: 'Andy', age: 36}), + (timothy:Person {name: 'Timothy', age: 25}), + (peter:Person {name: 'Peter', age: 35}), + (lisa:Person {name: 'Lisa', age: 28}), + (john:Person {name: 'John', age: 40}), + (susan:Person {name: 'Susan', age: 22}), + (andy)-[:KNOWS {since: 2012}]->(timothy), + (andy)-[:KNOWS {since: 1999}]->(peter), + (peter)-[:KNOWS {since: 2005}]->(lisa), + (lisa)-[:KNOWS {since: 2010}]->(john), + (john)-[:KNOWS {since: 2015}]->(susan) ---- [[filter-on-node-label]] -=== Basic filtering +== Basic filtering -To filter nodes by label, write a label predicate after the `WHERE` keyword using `WHERE n:foo`: -.Filter by node label +.Filter on a node label [source, cypher] ---- MATCH (n) WHERE n:Swedish -RETURN n.name, n.age +RETURN n.name AS name ---- -The `name` and `age` values for `Andy` are returned: - .Result -[role="queryresult",options="header,footer",cols="2*(f) WHERE k.since < 2000 -RETURN f.name, f.age, f.email +RETURN f.name AS name ---- -The `name`, `age` and `email` values for `Peter` are returned because `Andy` has known him since before 2000: - .Result -[role="queryresult",options="header,footer",cols="3* -| "Timothy" | 25 | -3+|Rows: 3 +1+d|Rows: 1 |=== +The name for `Timothy` is returned because the `WHERE` clause still acts as a filter on the `MATCH`. +However, `WITH` still reduces the scope for the rest of the query moving forward. +In this case, `name` is the only variable in scope for the `RETURN` clause. [[pattern-element-predicates]] -== Filter fixed-length patterns +== Filter patterns -`WHERE` clauses can be added to pattern elements in order to specify additional constraints. +`WHERE` clauses can be added to fixed-length and variable-length patterns in order to specify additional constraints. -`WHERE` can appear inside a node pattern in a `MATCH` clause or a pattern comprehension: +=== Fixed-length patterns -.Using `WHERE` inside a node pattern +.`WHERE` inside a node pattern [source, cypher] ---- WITH 30 AS minAge MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(b:Person WHERE b.age > minAge) -RETURN b.name +RETURN b.name AS name ---- .Result [role="queryresult",options="header,footer",cols="1*(b WHERE b:Person) | b.name] AS friends ---- WITH 2000 AS minYear MATCH (a:Person)-[r:KNOWS WHERE r.since < minYear]->(b:Person) -RETURN r.since +RETURN r.since AS knowsSince ---- .Result [role="queryresult",options="header,footer",cols="1*(b:Person) -WHERE r.since < minYear -RETURN r.since +MATCH (a:Person {name: 'Andy'}) +RETURN [(a)-[r:KNOWS WHERE r.since < minYear]->(b:Person) | r.since] AS years ---- .Result [role="queryresult",options="header,footer",cols="1*(b:Person) | r.since] AS years +MATCH p = (a:Person {name: "Andy"}) + ((:Person)-[r:KNOWS]->(:Person WHERE r.since < 2011)) {1,3} +RETURN [n IN nodes(p) | n.name] AS paths ---- +Note that any paths including `Timothy` are excluded by the `WHERE` predicate (`Andy` knows him since `2012`). + .Result [role="queryresult",options="header,footer",cols="1*(b:Person) +RETURN [n IN nodes(p) | n.name] AS path +---- From c5ca00f808e690d8eb584155a7cb592ed1790761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:34:03 +0100 Subject: [PATCH 24/32] everything but path pattern expressions --- modules/ROOT/content-nav.adoc | 1 - modules/ROOT/images/graph_where_clause.svg | 2 +- modules/ROOT/pages/clauses/where.adoc | 79 +++--- .../expressions/expressions-overview.adoc | 2 +- .../predicate-expressions/operators.adoc | 127 ++++++---- .../path-pattern-expressions.adoc | 39 ++- .../ROOT/pages/subqueries/existential.adoc | 2 +- modules/ROOT/pages/syntax/operators.adoc | 239 +----------------- modules/ROOT/pages/syntax/parsing.adoc | 2 +- 9 files changed, 150 insertions(+), 343 deletions(-) diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 61e110324..6ba476a91 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -61,7 +61,6 @@ ** xref:values-and-types/lists.adoc[] ** xref:values-and-types/maps.adoc[] ** xref:values-and-types/casting-data.adoc[] -** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] diff --git a/modules/ROOT/images/graph_where_clause.svg b/modules/ROOT/images/graph_where_clause.svg index b307c8355..f15e25ef6 100644 --- a/modules/ROOT/images/graph_where_clause.svg +++ b/modules/ROOT/images/graph_where_clause.svg @@ -1 +1 @@ -KNOWSsince:2012KNOWSsince:1999PersonSwedishname:'Andy'age:36belt:'white'Personname:'Timothy'age:25Personname:'Peter'age:35email:'peter_n@example.com' \ No newline at end of file +KNOWSsince:2012KNOWSsince:1999KNOWSsince:2005KNOWSsince:2010KNOWSsince:2021SwedishPersonname:'Andy'age:36Personname:'Timothy'age:38Personname:'Peter'age:35Personname:'Lisa'age:48Personname:'John'age:40Personname:'Susan'age:32 \ No newline at end of file diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index f8ff31db1..d412c4528 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -22,29 +22,28 @@ For more uses of `WHERE`, see xref:expressions/predicate-expressions/index.adoc[ The following graph is used for the examples below: -image::graph_where_clause.svg[width="600",role="middle"] +image::graph_where_clause.svg[width="700",role="middle"] To recreate the graph, run the following query in an empty Neo4j database: [source, cypher, role=test-setup] ---- CREATE (andy:Swedish:Person {name: 'Andy', age: 36}), - (timothy:Person {name: 'Timothy', age: 25}), + (timothy:Person {name: 'Timothy', age: 38}), (peter:Person {name: 'Peter', age: 35}), - (lisa:Person {name: 'Lisa', age: 28}), + (lisa:Person {name: 'Lisa', age: 48}), (john:Person {name: 'John', age: 40}), - (susan:Person {name: 'Susan', age: 22}), + (susan:Person {name: 'Susan', age: 32}), (andy)-[:KNOWS {since: 2012}]->(timothy), (andy)-[:KNOWS {since: 1999}]->(peter), (peter)-[:KNOWS {since: 2005}]->(lisa), (lisa)-[:KNOWS {since: 2010}]->(john), - (john)-[:KNOWS {since: 2015}]->(susan) + (john)-[:KNOWS {since: 2021}]->(susan) ---- -[[filter-on-node-label]] +[[basic-filtering]] == Basic filtering - .Filter on a node label [source, cypher] ---- @@ -67,7 +66,7 @@ RETURN n.name AS name [source, cypher] ---- MATCH (n:Person) -WHERE n.age < 30 +WHERE n.age < 35 RETURN n.name AS name, n.age AS age ---- @@ -76,25 +75,23 @@ RETURN n.name AS name, n.age AS age |=== | name | age -| "Timothy" | 25 -| "Lisa" | 28 -| "Susan" | 22 +| "Susan" | 32 -2+d|Rows: 3 +2+d|Rows: 1 |=== .Filter on a relationship property [source, cypher] ---- -MATCH (n:Person)-[k:KNOWS]->(f) +MATCH (:Person {name:'Andy'})-[k:KNOWS]->(f) WHERE k.since < 2000 -RETURN f.name AS name +RETURN f.name AS oldFriend ---- .Result [role="queryresult",options="header,footer",cols="1* 40 RETURN n.name AS name, n.age AS age ---- @@ -128,22 +125,20 @@ RETURN n.name AS name, n.age AS age |=== | name | age -| "Timothy" | 25 -| "Lisa" | 28 -| "Susan" | 22 +| "Lisa" | 48 -2+d|Rows: 3 +2+d|Rows: 1 |=== -[[usage-with-with-clause]] +[[where-and-with]] == Using `WHERE` after `WITH` xref:clauses/with.adoc[`WITH`] can be used to manipulate the output of a clause before it is passed on to subsequent query parts. Once such a manipulation has occurred, the original clause output is not available to subsequent clauses. For example, in the below query, `WITH` manipulates the output of the preceding `MATCH` in such a way that the succeeding `RETURN` no longer has access to the variable `n` declared in the `MATCH`. -.`WITH` manipulating clause output +.`WITH` only retains explicitly listed variables; others become inaccessible [source, cypher, role=test-fail] ---- MATCH (n:Person) @@ -159,7 +154,7 @@ However, because `WHERE` is a subclause and not a clause, its scope is not limit ---- MATCH (n:Person) WITH n.name as name -WHERE n.age = 25 +WHERE n.age = 38 RETURN name ---- @@ -177,17 +172,18 @@ The name for `Timothy` is returned because the `WHERE` clause still acts as a fi However, `WITH` still reduces the scope for the rest of the query moving forward. In this case, `name` is the only variable in scope for the `RETURN` clause. -[[pattern-element-predicates]] +[[filter-patterns]] == Filter patterns -`WHERE` clauses can be added to fixed-length and variable-length patterns in order to specify additional constraints. +`WHERE` clauses can be added to xref:patterns/fixed-length-patterns.adoc[fixed-length] and xref:patterns/variable-length-patterns.adoc[variable-length patterns] in order to specify additional constraints. +[[fixed-length-patterns]] === Fixed-length patterns .`WHERE` inside a node pattern [source, cypher] ---- -WITH 30 AS minAge +WITH 35 AS minAge MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(b:Person WHERE b.age > minAge) RETURN b.name AS name ---- @@ -197,7 +193,7 @@ RETURN b.name AS name |=== | name -| "Peter" +| "Timothy" 1+d|Rows: 1 |=== @@ -228,17 +224,17 @@ RETURN [(a)-->(b WHERE b:Person) | b.name] AS friends ---- WITH 2000 AS minYear MATCH (a:Person)-[r:KNOWS WHERE r.since < minYear]->(b:Person) -RETURN r.since AS knowsSince +RETURN a.name AS person, b.name AS friend, r.since AS knowsSince ---- .Result -[role="queryresult",options="header,footer",cols="1*(b:Person) | r.since] AS years 1+d|Rows: 1 |=== +[[variable-length-patterns]] === Variable-length patterns -If matching for variable length patterns, `WHERE` can only be used together with the xref:patterns/variable-length-patterns.adoc#quantified-path-patterns[quantified path pattern] syntax. +If matching for variable length patterns, `WHERE` can only be used together with the xref:patterns/variable-length-patterns.adoc#quantified-path-patterns[quantified path pattern] or xref:patterns/variable-length-patterns.adoc#quantified-relationships[quantified relationships] syntax. -.Allowed - `WHERE` predicate inside a quantified path pattern +.Allowed - `WHERE` predicate inside a quantified relationship [source, cypher] ---- -MATCH p = (a:Person {name: "Andy"}) - ((:Person)-[r:KNOWS]->(:Person WHERE r.since < 2011)) {1,3} +MATCH p = (a:Person {name: "Andy"})-[r:KNOWS WHERE r.since < 2011]->{1,4}(:Person) RETURN [n IN nodes(p) | n.name] AS paths ---- -Note that any paths including `Timothy` are excluded by the `WHERE` predicate (`Andy` knows him since `2012`). +Note that any path´s including `Timothy` and `Susan` are excluded by the `WHERE` predicate, since their incoming `KNOWS` relationships both have a `since` value that is higher than `2011.` .Result [role="queryresult",options="header,footer",cols="1*(b:Person) +MATCH p = (a:Person {name: 'Andy'})-[r:KNOWS*1..4 WHERE r.since < 2011]->(b:Person) RETURN [n IN nodes(p) | n.name] AS path ---- diff --git a/modules/ROOT/pages/expressions/expressions-overview.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc index 9e6ad2cf0..3cd1188bd 100644 --- a/modules/ROOT/pages/expressions/expressions-overview.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -18,7 +18,7 @@ For more information, see xref:clauses/where.adoc#filter-on-dynamic-property[`WH * A xref:patterns/fixed-length-patterns.adoc#path-patterns[path-pattern]: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. * An xref:syntax/operators.adoc[operator application]: `1 + 2`, `3 < 4`. * A xref:subqueries/index.adoc[subquery expression]: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. -* A xref:clauses/where.adoc#query-where-regex[regular expression]: `a.name =~ 'Tim.*'`. +* A xref:expressions/predicate-expressions/operators#regular-expressions[regular expression]: `a.name =~ 'Tim.*'`. * A xref:expressions/conditional-expressions.adoc[`CASE` expression]. * xref:values-and-types/working-with-null[`null`]. diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc index 0076d246e..9e153c597 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -18,25 +18,24 @@ To recreate the graph, run the following query in an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE - (alice:Person {name:'Alice', age: 65, email: 'alice@company.com', occupation: 'Project manager'}), - (cecil:Person {name: 'Cecil', age: 25, email: 'bob@company.com', occupation: 'Software developer'}), - (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), - (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), - (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@company.com', occupation: 'Director'}), - (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@private.se'}), - (cecil)-[:WORKS_FOR]->(alice), - (cecilia)-[:WORKS_FOR]->(alice), - (alice)-[:WORKS_FOR]->(daniel), - (charlie)-[:WORKS_FOR]->(daniel), - (bob)-[:LOVES]->(eskil), - (charlie)-[:LOVES]->(alice) +CREATE (alice:Person {name:'Alice', age: 65, email: 'alice@company.com', occupation: 'Project manager'}), + (cecil:Person {name: 'Cecil', age: 25, email: 'bob@company.com', occupation: 'Software developer'}), + (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@company.com', occupation: 'Director'}), + (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@private.se'}), + (cecil)-[:WORKS_FOR]->(alice), + (cecilia)-[:WORKS_FOR]->(alice), + (alice)-[:WORKS_FOR]->(daniel), + (charlie)-[:WORKS_FOR]->(daniel), + (cecil)-[:LOVES]->(eskil), + (charlie)-[:LOVES]->(alice) ---- [[boolean-operators]] == Boolean operators -The predicate boolean operators comprise: +The boolean operators comprise: * Conjunction: `AND` * Disjunction: `OR` @@ -123,20 +122,21 @@ RETURN n.name AS name, n.age AS age, n.occupation AS occupation [source, cypher] ---- MATCH (n:Person) -WHERE n.age > 30 AND NOT n.occupation = 'Software developer' -RETURN n.name AS name, n.age AS age, n.occupation AS occupation +WHERE NOT (n)-[:LOVES]->(:Person) +RETURN n.name AS name ---- .Result -[role="queryresult",options="header,footer",cols="3*` @@ -461,7 +461,7 @@ For more information about `LIST` and `MAP` values, see xref::values-and-types/p [[ordering-spatial-temporal]] ==== Ordering spatial and temporal values -The following applies to ordering of xref:values-and-types/spatial.adoc[spatial types]: +The following applies to the ordering of xref:values-and-types/spatial.adoc[spatial types]: * `POINT` values are ordered after lists and before temporal types. * `POINT` values of different coordinate reference systems (CRS) are ordered by the CRS code (the value of SRID field). @@ -481,7 +481,7 @@ For example, `DATETIME` is considered "greater" than a `DATE` and `2023-02-10T12 ** 1 month is treated as 30.436875 days (which is 1/12 of a year). ** 1 day is always 24 hours. -The following applies to comparing temporal types: +The following applies to the comparison of temporal types: * xref::values-and-types/temporal.adoc#cypher-temporal-instants[Temporal instant values] (like `DATETIME` and `DATE`) can be compared if they are of the same type. An earlier instant is considered smaller (less than) compared to a later instant. @@ -491,7 +491,38 @@ If two instants have the same time but different time zones, they are ordered by If they have the same time and offset but different named time zones, they are sorted alphabetically by the time zone name. * Duration values cannot be directly compared. Since the length of a day, month, or year varies, Cypher does not define a strict ordering for durations. -Compare two durations `(e.g, duration1 < duration2)`, the result will always be `null`. +As a result, comparing two durations `(e.g, duration1 < duration2)` will always return `null`. + +[[chaining-comparison-oeprators]] +=== Chaining comparison operators + +There is no limit on how many comparisons can be chained together. +If chaining two or more comparison operators, each comparison is effectively separated by an `AND` operator (though this `AND` is not required syntactically). +For example, if `a, b, c, ..., z` are expressions and `op1, op2, ..., opN` are comparison operators, then the following expressions are equivalent: + +.Equivalent expressions +[source, syntax] +---- +a op1 b op2 c ... y opN z; +a op1 b AND b op2 c AND ... y opN z +---- + +Note that `a op1 b op2 c` does not imply any kind of comparison between `a` and `c`. +For example, in `x < y > z`, `x` and `z` are not compared. + +[[chaining-equality-operators]] +==== Chaining equality operators + +Chains of `=` and `<>` are treated in a special way in Cypher. +Specifically, `1=1=true` is equivalent to `1=1 AND 1=true` and not to `(1=1)=true` or `1=(1=true)`. +For example, the following expressions are equivalent. + +.Equivalent expressions +[source, syntax, role=noplay] +---- +a < b = c <= d <> e; +a < b AND b = c AND c <= d AND d <> e +---- [[string-operators]] == String operators @@ -506,7 +537,7 @@ The predicate string operators comprise: * `IS NOT NORMALIZED` These operators perform _case-sensitive_ matching. -Attempting to use them operators on values which are not `STRING` values will return `null`. +Attempting to use them on values which are not `STRING` values will return `null`. .Prefix, suffix, and substring operators ===== @@ -603,9 +634,9 @@ RETURN n.name AS name, n.email AS email 2+d|Rows: 3 |=== -By pre-pending a regular expression with `(?i)`, the whole expression becomes case-insensitive: +By pre-pending a regular expression with the flag `(?i)`, the whole expression becomes case-insensitive: -.Case insensitive regular expression`(?i)` +.Case insensitive regular expression `(?i)` [source, cypher] ---- MATCH (n:Person) @@ -613,7 +644,7 @@ WHERE n.name =~ '(?i)CEC.*' RETURN n.name ---- -The names of both `Cecil` and `Cecilia` are returned because their name starts with `'CEC'` irrespective of casing: +The names of both `Cecil` and `Cecilia` are returned because their name starts with `'CEC'` regardless of casing: .Result [role="queryresult",options="header,footer",cols="2*(alice), + (cecilia)-[:WORKS_FOR {since: 2015}]->(alice), + (alice)-[:WORKS_FOR {since: 2020}]->(daniel), + (charlie)-[:WORKS_FOR {since: 2003}]->(daniel), + (daniel)-[:WORKS_FOR {since: 2024}]->(eskil) +---- + [[filter-on-patterns]] -=== Filter on patterns +=== Filter on fixed-length patterns .Query [source, cypher] ---- MATCH - (timothy:Person {name: 'Timothy'}), + (cecil:Person {name: 'Cecil'}), (other:Person) -WHERE (other)-->(timothy) +WHERE (cecil)-->(other) RETURN other.name, other.age ---- -The `name` and `age` values for nodes that have an outgoing relationship to `Timothy` are returned: - .Result [role="queryresult",options="header,footer",cols="2*+`, `+<+`, `+>+`, `+<=+`, `+>=+`, `IS NULL`, `IS NOT NULL` -| xref::syntax/operators.adoc#query-operators-comparison[`STRING`-specific comparison operators] | `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `=~` (regex matching) | xref::syntax/operators.adoc#query-operators-string[String operators] | `+` and `\|\|` (string concatenation), `IS NORMALIZED` | xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] | `+` and `-` for operations between durations and temporal instants/durations, `*` and `/` for operations between durations and numbers | xref::syntax/operators.adoc#query-operators-map[Map operators] | `.` for static value access by key, `[]` for dynamic value access by key @@ -130,7 +128,7 @@ RETURN DISTINCT restaurant.name 1+d|Rows: 1 |=== -See xref::clauses/where.adoc#query-where-basic[Basic usage] for more details on dynamic property access. +See also xref::clauses/where.adoc#filter-on-dynamic-properties[`WHERE` -> Filter on dynamic properties]. [NOTE] ==== @@ -244,239 +242,6 @@ RETURN b - a AS result |=== -[[query-operators-comparison]] -== Comparison operators - -The comparison operators comprise: - -* equality: `+=+` -* inequality: `+<>+` -* less than: `+<+` -* greater than: `+>+` -* less than or equal to: `+<=+` -* greater than or equal to: `+>=+` -* `IS NULL` -* `IS NOT NULL` - - -[[query-operator-comparison-string-specific]] -=== `STRING`-specific comparison operators comprise: - -* `STARTS WITH`: perform case-sensitive prefix searching on `STRING` values. -* `ENDS WITH`: perform case-sensitive suffix searching on `STRING` values. -* `CONTAINS`: perform case-sensitive inclusion searching in `STRING` values. -* `=~`: regular expression for matching a pattern. - -[[syntax-comparing-two-numbers]] -=== Comparing two numbers - -.Query -[source, cypher] ----- -WITH 4 AS one, 3 AS two -RETURN one > two AS result ----- - -.Result -[role="queryresult",options="header,footer",cols="1*` operators. - -Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). - -Maps are only equal if they map exactly the same keys to equal values and lists are only equal if they contain the same sequence of equal values (e.g. `[3, 4] = [1+2, 8/2]`). - -Values of different types are considered as equal according to the following rules: - -* Paths are treated as lists of alternating nodes and relationships and are equal to all lists that contain that very same sequence of nodes and relationships. -* Testing any value against `null` with both the `=` and the `<>` operators always evaluates to `null`. -This includes `null = null` and `null <> null`. -The only way to reliably test if a value `v` is `null` is by using the special `v IS NULL`, or `v IS NOT NULL`, equality operators. -`v IS NOT NULL` is equivalent to `NOT(v IS NULL)`. - -All other combinations of types of values cannot be compared with each other. -Especially, nodes, relationships, and literal maps are incomparable with each other. - -It is an error to compare values that cannot be compared. - - -[[cypher-ordering]] -=== Ordering and comparison of values - -The comparison operators `+<=+`, `+<+` (for ascending) and `+>=+`, `+>+` (for descending) are used to compare values for ordering. -The following points give some details on how the comparison is performed. - -* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is true). -* All comparability tests (`+<+`, `+<=+`, `+>+`, `+>=+`) with `java.lang.Double.NaN` evaluate as false. -For example, `1 > b` and `1 < b` are both false when b is NaN. -* String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). -* Boolean values are compared for ordering such that `false < true`. -* Spatial values cannot be compared using the operators `<`, `+<=+`, `>`, or `>=`. -To compare spatial values within a specific range, use either the xref:functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] or the xref:functions/spatial.adoc#functions-point-wgs84-2d[`point()`] function. - -* *Ordering* of spatial values: - ** `ORDER BY` requires all values to be orderable. - ** Points are ordered after arrays and before temporal types. - ** Points of different CRS are ordered by the CRS code (the value of SRID field). For the currently supported set of xref::values-and-types/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] this means the order: 4326, 4979, 7302, 9157 - ** Points of the same CRS are ordered by each coordinate value in turn, `x` first, then `y` and finally `z`. - ** Note that this order is different to the order returned by the spatial index, which will be the order of the space filling curve. -* *Comparison* of temporal values: - ** xref::values-and-types/temporal.adoc#cypher-temporal-instants[Temporal instant values] are comparable within the same type. - An instant is considered less than another instant if it occurs before that instant in time, and it is considered greater than if it occurs after. - ** Instant values that occur at the same point in time -- but that have a different time zone -- are not considered equal, and must therefore be ordered in some predictable way. - Cypher prescribes that, after the primary order of point in time, instant values be ordered by effective time zone offset, from west (negative offset from UTC) to east (positive offset from UTC). - This has the effect that times that represent the same point in time will be ordered with the time with the earliest local time first. - If two instant values represent the same point in time, and have the same time zone offset, but a different named time zone (this is possible for _DateTime_ only, since _Time_ only has an offset), these values are not considered equal, and ordered by the time zone identifier, alphabetically, as its third ordering component. - If the type, point in time, offset, and time zone name are all equal, then the values are equal, and any difference in order is impossible to observe. - ** xref::values-and-types/temporal.adoc#cypher-temporal-durations[_Duration_] values cannot be compared, since the length of a _day_, _month_ or _year_ is not known without knowing which _day_, _month_ or _year_ it is. - Since _Duration_ values are not comparable, the result of applying a comparison operator between two _Duration_ values is `null`. -* *Ordering* of temporal values: - ** `ORDER BY` requires all values to be orderable. - ** Temporal instances are ordered after spatial instances and before strings. - ** Comparable values should be ordered in the same order as implied by their comparison order. - ** Temporal instant values are first ordered by type, and then by comparison order within the type. - ** Since no complete comparison order can be defined for _Duration_ values, we define an order for `ORDER BY` specifically for _Duration_: - *** _Duration_ values are ordered by normalising all components as if all years were `365.2425` days long (`PT8765H49M12S`), all months were `30.436875` (`1/12` year) days long (`PT730H29M06S`), and all days were `24` hours long footnote:[The `365.2425` days per year comes from the frequency of leap years. - A leap year occurs on a year with an ordinal number divisible by `4`, that is not divisible by `100`, unless it divisible by `400`. - This means that over `400` years there are `((365 * 4 + 1) * 25 - 1) * 4 + 1 = 146097` days, which means an average of `365.2425` days per year.]. -* Comparing for ordering when one argument is `null` (e.g. `null < 3` is `null`). -* *Ordering* of values with *different* types: - ** The ordering is, in ascending order, defined according to the following list: - *** xref::values-and-types/maps.adoc#cypher-literal-maps[`MAP`] - *** xref::values-and-types/property-structural-constructed.adoc#structural-types[`NODE`] - *** xref::values-and-types/property-structural-constructed.adoc#structural-types[`RELATIONSHIP`] - *** xref::values-and-types/lists.adoc[`LIST`] - *** xref::patterns/fixed-length-patterns.adoc#path-patterns[`PATH`] - *** xref::values-and-types/temporal.adoc[`ZONED DATETIME`] - *** xref::values-and-types/temporal.adoc[`LOCAL DATETIME`] - *** xref::values-and-types/temporal.adoc[`DATE`] - *** xref::values-and-types/temporal.adoc[`ZONED TIME`] - *** xref::values-and-types/temporal.adoc[`LOCAL TIME`] - *** xref::values-and-types/temporal.adoc[`DURATION`] - *** xref::expressions/expressions-overview.adoc#string[`STRING`] - *** xref::expressions/expressions-overview.adoc#boolean[`BOOLEAN`] - *** Numbers: xref::expressions/expressions-overview.adoc#numerical[`INTEGER`, `FLOAT`] - ** The value `null` is ordered after all other values. -* *Ordering* of constructed type values: - ** For the xref::values-and-types/property-structural-constructed.adoc#constructed-types[constructed types] (e.g. maps and lists), elements of the containers are compared pairwise for ordering and thus determine the ordering of two container types. -For example, `[1, 'foo', 3]` is ordered before `[1, 2, 'bar']` since `'foo'` is ordered before `2`. - - -[[cypher-operations-chaining]] -=== Chaining comparison operations - -Comparisons can be chained arbitrarily, e.g., `+x < y <= z+` is equivalent to `+x < y AND y <= z+`. - -Formally, if `+a, b, c, ..., y, z+` are expressions and `+op1, op2, ..., opN+` are comparison operators, then `+a op1 b op2 c ... y opN z+` is equivalent to `+a op1 b and b op2 c and ... y opN z+`. - -Note that `a op1 b op2 c` does not imply any kind of comparison between `a` and `c`, so that, e.g., `x < y > z` is perfectly legal (although perhaps not elegant). - -The example: - -[source, cypher] ----- -MATCH (n) WHERE 21 < n.age <= 30 RETURN n ----- - -is equivalent to - -[source, cypher] ----- -MATCH (n) WHERE 21 < n.age AND n.age <= 30 RETURN n ----- - -Thus, it matches all nodes where the age is between 21 and 30. - -This syntax extends to all equality `=` and inequality `<>` comparisons, as well as to chains longer than three. - -[NOTE] -==== -Chains of `=` and `<>` are treated in a special way in Cypher. - -This means that `1=1=true` is equivalent to `1=1 AND 1=true` and not to `(1=1)=true` or `1=(1=true)`. -==== - -For example: - -[source, syntax, role=noplay] ----- -a < b = c <= d <> e ----- - -Is equivalent to: - -[source, syntax, role=noplay] ----- -a < b AND b = c AND c <= d AND d <> e ----- - - -[[syntax-using-a-regular-expression-to-filter-words]] -=== Using a regular expression with `=~` to filter words - -.Query -[source, cypher] ----- -WITH ['mouse', 'chair', 'door', 'house'] AS wordlist -UNWIND wordlist AS word -WITH word -WHERE word =~ '.*ous.*' -RETURN word ----- - -.Result -[role="queryresult",options="header,footer",cols="1* Date: Wed, 19 Feb 2025 16:10:05 +0100 Subject: [PATCH 25/32] path pattern expressions --- .../ROOT/images/path_pattern_expressions.svg | 1 + modules/ROOT/images/predicate_operators.svg | 1 + .../predicate-expressions/index.adoc | 6 +- .../predicate-expressions/operators.adoc | 113 +++++++++--------- .../path-pattern-expressions.adoc | 104 ++++++++++------ 5 files changed, 130 insertions(+), 95 deletions(-) create mode 100644 modules/ROOT/images/path_pattern_expressions.svg create mode 100644 modules/ROOT/images/predicate_operators.svg diff --git a/modules/ROOT/images/path_pattern_expressions.svg b/modules/ROOT/images/path_pattern_expressions.svg new file mode 100644 index 000000000..e236e0637 --- /dev/null +++ b/modules/ROOT/images/path_pattern_expressions.svg @@ -0,0 +1 @@ +WORKS_FORsince:2023WORKS_FORsince:2015Personname:'Alice'age:65role:'Project manager'Personname:'Cecil'age:25role:'Software developer'Personname:'Cecilia'age:31role:'Software developer' \ No newline at end of file diff --git a/modules/ROOT/images/predicate_operators.svg b/modules/ROOT/images/predicate_operators.svg new file mode 100644 index 000000000..d1cc6b308 --- /dev/null +++ b/modules/ROOT/images/predicate_operators.svg @@ -0,0 +1 @@ +Personname:'Alice'age:65role:'Project manager'email:'alice@company.com'Personname:'Cecil'age:25role:'Software developer'email:'cecil@private.se'Personname:'Cecilia'age:31role:'Software developer'Personname:'Daniel'age:39role:'Director'email:'daniel@company.com'Personname:'Eskil'age:39role:'CEO'email:'eskil@company.com'Personname:'Charlie'age:61role:'Security engineer' \ No newline at end of file diff --git a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc index a8eaac62e..76f756799 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc @@ -1,6 +1,7 @@ = Predicate expressions +:Description: Overview of the predicate expressions in Cypher. -A predicate is a `BOOLEAN` expression that evaluates to `TRUE`, `FALSE`, or `NULL` +Predicate expressions evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`). Predicate expressions are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. This chapter is divided into the following sections: @@ -13,6 +14,5 @@ The following can also serve as a predicate expressions (but are documented else * xref:syntax/variables.adoc[] * xref:queries/concepts.adoc[Properties] (See also xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]) -* xref:functions/index.adoc[] returning a `BOOLEAN`. -For more information, see xref:functions/predicate.adoc[Predicate functions]. +* xref:functions/predicate.adoc[Predicate functions]. * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc index 9e153c597..ae35fe752 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -1,5 +1,6 @@ = Predicate operators :table-caption!: +:description: Information about using predicate operators in Cypher (including boolean, comparison, string, and list operators). Predicate operators are used to evaluate expressions and return `BOOLEAN` values. They are categorized as follows: @@ -14,22 +15,18 @@ They are categorized as follows: The following graph is used for the examples below: +image::predicate_operators.svg[width="500",role="middle"] + To recreate the graph, run the following query in an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE (alice:Person {name:'Alice', age: 65, email: 'alice@company.com', occupation: 'Project manager'}), - (cecil:Person {name: 'Cecil', age: 25, email: 'bob@company.com', occupation: 'Software developer'}), - (cecilia:Person {name: 'Cecilia', age: 31, occupation: 'Software developer'}), - (charlie:Person {name: 'Charlie', age: 61, occupation: 'Security engineer'}), - (daniel:Person {name: 'Daniel', age: 39, email: 'daniel@company.com', occupation: 'Director'}), - (eskil:Person {name: 'Eskil', age: 39, email: 'eskil@private.se'}), - (cecil)-[:WORKS_FOR]->(alice), - (cecilia)-[:WORKS_FOR]->(alice), - (alice)-[:WORKS_FOR]->(daniel), - (charlie)-[:WORKS_FOR]->(daniel), - (cecil)-[:LOVES]->(eskil), - (charlie)-[:LOVES]->(alice) +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) ---- [[boolean-operators]] @@ -64,14 +61,14 @@ The boolean operators comprise: [source, cypher] ---- MATCH (n:Person) -WHERE n.age > 30 AND n.occupation = 'Software developer' -RETURN n.name AS name, n.age AS age, n.occupation AS occupation +WHERE n.age > 30 AND n.role = 'Software developer' +RETURN n.name AS name, n.age AS age, n.role AS role ---- .Result [role="queryresult",options="header,footer",cols="3* 30 XOR n.occupation = 'Software developer' -RETURN n.name AS name, n.age AS age, n.occupation AS occupation +WHERE n.age > 30 XOR n.role = 'Software developer' +RETURN n.name AS name, n.age AS age, n.role AS role ---- .Result -[role="queryresult",options="header,footer",cols="3*(:Person) -RETURN n.name AS name +WHERE NOT n.age = 39 +RETURN n.name AS name, n.age AS age ---- .Result -[role="queryresult",options="header,footer",cols="1* 60 AND n.occupation = 'Security engineer') OR NOT (n.occupation = 'Director' OR n.name = 'Eskil') -RETURN n.name AS name, n.age AS age, n.occupation AS occupation +WHERE n.role = 'Software developer' XOR (n.age > 60 AND n.role = 'Security engineer') OR NOT (n.role = 'Director' OR n.name = 'Eskil') +RETURN n.name AS name, n.age AS age, n.role AS role ---- .Result [role="queryresult",options="header,footer",cols="3* 'Software developer' -RETURN n.name AS name, n.occupation AS occupation +WHERE n.role <> 'Software developer' +RETURN n.name AS name, n.role AS role ---- .Result [role="queryresult",options="header,footer",cols="2*(alice), - (cecilia)-[:WORKS_FOR {since: 2015}]->(alice), - (alice)-[:WORKS_FOR {since: 2020}]->(daniel), - (charlie)-[:WORKS_FOR {since: 2003}]->(daniel), - (daniel)-[:WORKS_FOR {since: 2024}]->(eskil) + (cecilia)-[:WORKS_FOR {since: 2015}]->(alice) ---- [[filter-on-patterns]] -=== Filter on fixed-length patterns +== Examples -.Query +.Simple path pattern expression [source, cypher] ---- -MATCH - (cecil:Person {name: 'Cecil'}), - (other:Person) -WHERE (cecil)-->(other) -RETURN other.name, other.age +MATCH (manager:Person {name: 'Alice'}), + (employee:Person) +WHERE (employee)-[:WORKS_FOR]->(manager) +RETURN employee.name AS employee ---- .Result -[role="queryresult",options="header,footer",cols="2*(:Person {name: 'Alice'}) +RETURN employee.name AS employee; +---- + + +.Result +[role="queryresult",options="header,footer",cols="1* Boolean operators]. -The `NOT` operator can be used to exclude a pattern: +Patterns can be placed inside expressions. +For example: -.Query +.Pattern inside an expression [source, cypher] ---- -MATCH - (peter:Person {name: 'Peter'}), - (other:Person) -WHERE NOT (other)-->(peter) -RETURN other.name, other.age +RETURN NOT (:Person {name: "Alice"})<-[:WORKS_FOR {since: 2023}]-(:Person) AS patternCheck ---- -The `name` and `age` values for nodes that do not have an outgoing relationship to `Peter` are returned: - .Result -[role="queryresult",options="header,footer",cols="2*(:Person {name: "Alice"})) AS patternCheck +---- + +.Result +[role="queryresult",options="header,footer",cols="1* Date: Mon, 24 Feb 2025 09:47:48 +0100 Subject: [PATCH 26/32] clean up --- modules/ROOT/pages/clauses/where.adoc | 2 - modules/ROOT/pages/expressions/index.adoc | 6 +- .../predicate-expressions/index.adoc | 3 +- .../predicate-expressions/operators.adoc | 88 +++++++-------- .../path-pattern-expressions.adoc | 3 +- .../type-predicate-expressions.adoc | 106 +++++++++++------- 6 files changed, 113 insertions(+), 95 deletions(-) diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index d412c4528..ac9088fee 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -1,7 +1,5 @@ :description: `WHERE` adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. :table-caption!: - -[[query-where]] = WHERE The `WHERE` clause is not a clause in its own right -- rather, it is subclause used with xref:clauses/match.adoc[`MATCH`], xref:clauses/optional-match.adoc[`OPTIONAL MATCH`], and xref:clauses/with.adoc[`WITH`] clauses. diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index 4f6834b1e..99cca9ded 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -1,9 +1,7 @@ = 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: +A Cypher expression is any part of a query that evaluates to a value. +For details and examples of specific expressions, see the following sections: * xref:expressions/expressions-overview.adoc[] * xref:expressions/predicate-expressions/index.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc index 76f756799..6622bcc90 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc @@ -1,8 +1,7 @@ = Predicate expressions :Description: Overview of the predicate expressions in Cypher. -Predicate expressions evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`). -Predicate expressions are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. +Predicate expressions evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`), and are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. This chapter is divided into the following sections: diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc index ae35fe752..b1a2c2c63 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -6,7 +6,7 @@ Predicate operators are used to evaluate expressions and return `BOOLEAN` values They are categorized as follows: * xref:expressions/predicate-expressions/operators.adoc#boolean-operators[Boolean operators] (`AND`, `OR`, `XOR`, `NOT`) -* xref:expressions/predicate-expressions/operators.adoc#comparison-operators[Comparison operators] (`=`, `<>`, `<`, `>`, `<=`, `>=`, `IS NULL`, `IS NOT NULL`) +* xref:expressions/predicate-expressions/operators.adoc#comparison-operators[Comparison operators] (`=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`) * xref:expressions/predicate-expressions/operators.adoc#string-operators[String operators] (`=~`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`) * xref:expressions/predicate-expressions/operators.adoc#list-operators[List operators] (`IN`) @@ -43,15 +43,15 @@ The boolean operators comprise: [options="header", cols="^,^,^,^,^,^", width="85%"] |=== |a | b | a `AND` b | a `OR` b | a `XOR` b | `NOT` a -|`false` | `false` | `false` | `false` | `false` | `true` -|`false` | `null` | `false` | `null` | `null` | `true` -|`false` | `true` | `false` | `true` | `true` | `true` -|`true` | `false` | `false` | `true` | `true` | `false` -|`true` | `null` | `null` | `true` | `null` | `false` -|`true` | `true` | `true` | `true` | `false` | `false` -|`null` | `false` | `false` | `null` | `null` | `null` -|`null` | `null` | `null` | `null` | `null` | `null` -|`null` | `true` | `null` | `true` | `null` | `null` +|`FALSE` | `FALSE` | `FALSE` | `FALSE` | `FALSE` | `TRUE` +|`FALSE` | `NULL` | `FALSE` | `NULL` | `NULL` | `TRUE` +|`FALSE` | `TRUE` | `FALSE` | `TRUE` | `TRUE` | `TRUE` +|`TRUE` | `FALSE` | `FALSE` | `TRUE` | `TRUE` | `FALSE` +|`TRUE` | `NULL` | `NULL` | `TRUE` | `NULL` | `FALSE` +|`TRUE` | `TRUE` | `TRUE` | `TRUE` | `FALSE` | `FALSE` +|`NULL` | `FALSE` | `FALSE` | `NULL` | `NULL` | `NULL` +|`NULL` | `NULL` | `NULL` | `NULL` | `NULL` | `NULL` +|`NULL` | `TRUE` | `NULL` | `TRUE` | `NULL` | `NULL` |=== .Boolean operators @@ -176,7 +176,7 @@ The comparison operators comprise: * `IS NOT NULL` [TIP] -For more information about `null`, see xref:values-and-types/working-with-null.adoc[]. +For more information about `NULL`, see xref:values-and-types/working-with-null.adoc[]. .Comparison operators ===== @@ -218,7 +218,7 @@ RETURN n.name AS name, n.role AS role | "Daniel" | "Director" | "Eskil" | "CEO" -2+d|Rows: 3 +2+d|Rows: 4 |=== .Less than operator (`<`) @@ -356,9 +356,9 @@ Maps are only equal if they map exactly the same keys to equal values and lists Values of different types are considered as equal according to the following rules: * `PATH` values are treated as lists of alternating nodes and relationships and are equal to all lists that contain an identical sequence of nodes and relationships. -* Testing any value against `null` with either the `=` or `<>` operator always evaluates to `null`. -This includes `null = null` and `null <> null`. -To reliably test if a value is `null` use `v IS NULL` or `v IS NOT NULL` (the latter is equivalent to `NOT(v IS NULL)`). +* Testing any value against `NULL` with either the `=` or `<>` operator always evaluates to `NULL`. +This includes `NULL = NULL` and `NULL <> NULL`. +To reliably test if a value is `NULL` use `v IS NULL` or `v IS NOT NULL` (the latter is equivalent to `NOT(v IS NULL)`). Other combinations of value types cannot be compared with each other. For example, nodes, relationships, and literal maps cannot be compared to one another. @@ -370,12 +370,12 @@ Comparing incomparable values will throw an error. xref:clauses/order-by.adoc[`ORDER BY`] requires that all values are orderable. The following points explain how comparisons are made when using the `\<=`, `<`,`>=`, `>` operators. -* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is `true`). -* All comparability tests with `java.lang.Double.NaN` evaluate as `false`. -For example, `1 > b` and `1 < b` are both `false` when `b` is NaN. +* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is `TRUE`). +* All comparability tests with `java.lang.Double.NaN` evaluate as `FALSE`. +For example, `1 > b` and `1 < b` are both `FALSE` when `b` is NaN. * String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). -* Boolean values are compared for ordering such that `false < true`. -* When comparing values for ordering, if one of the arguments is `null`, the result is always `null`. +* Boolean values are compared for ordering such that `FALSE < TRUE`. +* When comparing values for ordering, if one of the arguments is `NULL`, the result is always `NULL`. * xref:values-and-types/spatial.adoc[Spatial values] cannot be compared using the operators `\<=`, `<`,`>=`, `>`. To compare spatial values within a specific range, use either the xref:functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] or the xref:functions/spatial.adoc#functions-point-wgs84-2d[`point()`] function. @@ -407,12 +407,12 @@ Values of different types are ordered based on a predefined hierarchy, from leas * Numbers: xref::expressions/expressions-overview.adoc#numerical[`INTEGER`, `FLOAT`] [NOTE] -`null` is ordered after all other values. +`NULL` is ordered after all other values. .Sorting rules for mixed Cypher types [source, cypher] ---- -WITH [42, "hello", null, true, {name: "Alice"}, [1, 2, 3], date("2024-02-10")] AS v +WITH [42, "hello", NULL, true, {name: "Alice"}, [1, 2, 3], date("2024-02-10")] AS v UNWIND v AS values RETURN values ORDER BY values @@ -443,17 +443,17 @@ ORDER BY values * Elements are compared pairwise from the start to the end of the list. The order is defined by the first pair where the elements differ. For example, `[1, 'foo', 3]` is less than `[1, 2, 'bar']` because `'foo'` is less than `2`. -* If one list is shorter, it is padded with an empty element, which is considered less than any other value (including `null`). -For example, `[1, 'foo']` is less than `[1, 'foo', 3]`, and `[1]` is less than `[1, null]`. +* If one list is shorter, it is padded with an empty element, which is considered less than any other value (including `NULL`). +For example, `[1, 'foo']` is less than `[1, 'foo', 3]`, and `[1]` is less than `[1, NULL]`. *Maps are ordered by size, keys, and values:* * Maps are compared primarily by size: the smallest map has the fewest entries. For example, `{a: 1}` is less than `{a: 0, b: 'foo'}`. * Maps of equal size are compared by the alphabetical order of their keys. -For example, `{b: 100, a: 'foo'}` is less than `{a: '', c: null}` because `['a', 'b']` is less than `['a', 'c']`. +For example, `{b: 100, a: 'foo'}` is less than `{a: '', c: NULL}` because `['a', 'b']` is less than `['a', 'c']`. * For maps with identical key sets, the comparison is based on the values. After sorting keys alphabetically, values are compared pairwise. -For example, `{b: 100, a: 'foo'}` is less than `{a: 'foo', b: null}` because `['foo', 100]` is less than `['foo', null]`. +For example, `{b: 100, a: 'foo'}` is less than `{a: 'foo', b: NULL}` because `['foo', 100]` is less than `['foo', NULL]`. For more information about `LIST` and `MAP` values, see xref::values-and-types/property-structural-constructed.adoc#constructed-types[Values and types -> Constructed types]. @@ -490,7 +490,7 @@ If two instants have the same time but different time zones, they are ordered by If they have the same time and offset but different named time zones, they are sorted alphabetically by the time zone name. * Duration values cannot be directly compared. Since the length of a day, month, or year varies, Cypher does not define a strict ordering for durations. -As a result, comparing two durations `(e.g, duration1 < duration2)` will always return `null`. +As a result, comparing two durations `(e.g, duration1 < duration2)` will always return `NULL`. [[chaining-comparison-oeprators]] === Chaining comparison operators @@ -526,7 +526,7 @@ a < b AND b = c AND c <= d AND d <> e [[string-operators]] == String operators -The predicate string operators comprise: +The string operators comprise: * Prefix: `STARTS WITH` (case sensitive) * Suffix: `ENDS WITH` (case sensitive) @@ -536,7 +536,7 @@ The predicate string operators comprise: * `IS NOT NORMALIZED` These operators perform _case-sensitive_ matching. -Attempting to use them on values which are not `STRING` values will return `null`. +Attempting to use them on values which are not `STRING` values will return `NULL`. .Prefix, suffix, and substring operators ===== @@ -607,7 +607,7 @@ RETURN n.name AS name, n.role AS role Cypher supports filtering using regular expressions. The regular expression syntax is inherited from the link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html[Java regular expressions]. -This includes support for flags that change how `STRING` values are matched, including case-insensitive `(?i)`, multiline `(?m)`, and dotall `(?s)`. +This includes support for flags that change how `STRING` values are matched, including the case-insensitive `(?i)`, multiline `(?m)`, and dotall `(?s)` flags. Flags are given at the beginning of the regular expression. .Regular expressions @@ -635,7 +635,7 @@ RETURN n.name AS name, n.email AS email By pre-pending a regular expression with the flag `(?i)`, the whole expression becomes case-insensitive: -.Case insensitive regular expression `(?i)` +.Case-insensitive regular expression `(?i)` [source, cypher] ---- MATCH (n:Person) @@ -646,7 +646,7 @@ RETURN n.name The names of both `Cecil` and `Cecilia` are returned because their name starts with `'CEC'` regardless of casing: .Result -[role="queryresult",options="header,footer",cols="2* Boolean operators]. Patterns can be placed inside expressions. -For example: .Pattern inside an expression [source, cypher] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc b/modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc index de79a17f5..ae276e739 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc @@ -19,17 +19,19 @@ For all available Cypher types, see the section on xref::values-and-types/proper [source, cypher] ---- -UNWIND [42, true, 'abc', null] AS val +UNWIND [42, true, 'abc', NULL] AS val RETURN val, val IS :: INTEGER AS isInteger ---- [role="queryresult",options="header,footer",cols="2* AS isIntList ---- [role="queryresult",options="header,footer",cols="2* as isMixedList [role="queryresult",options="header,footer",cols="1* Date: Mon, 24 Feb 2025 10:59:44 +0100 Subject: [PATCH 27/32] new ordering and equality page --- modules/ROOT/content-nav.adoc | 2 + .../predicate-expressions/operators.adoc | 150 +----------------- .../ROOT/pages/values-and-types/index.adoc | 1 + .../ordering-equality-comparison.adoc | 146 +++++++++++++++++ 4 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index ff8943651..6add790fe 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -65,6 +65,8 @@ ** xref:values-and-types/lists.adoc[] ** xref:values-and-types/maps.adoc[] ** xref:values-and-types/casting-data.adoc[] +** xref:values-and-types/ordering-equality-comparison.adoc[] + * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc index b1a2c2c63..38cc9cb52 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc @@ -176,7 +176,7 @@ The comparison operators comprise: * `IS NOT NULL` [TIP] -For more information about `NULL`, see xref:values-and-types/working-with-null.adoc[]. +For more information about how Cypher orders and compares different value types, see xref:values-and-types/ordering-equality-comparison.adoc[Values and types -> Equality, ordering, and comparison of value types] .Comparison operators ===== @@ -344,154 +344,6 @@ RETURN n.name AS name, n.email AS email ===== -[[equality-and-comparison]] -=== Equality and comparison of values - -The equality (`=`) and inequality (`<>`) operators allows for comparing the equality of different values. - -Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). - -Maps are only equal if they map exactly the same keys to equal values and lists are only equal if they contain the same sequence of equal values (e.g. `[3, 4] = [1+2, 8/2]`). - -Values of different types are considered as equal according to the following rules: - -* `PATH` values are treated as lists of alternating nodes and relationships and are equal to all lists that contain an identical sequence of nodes and relationships. -* Testing any value against `NULL` with either the `=` or `<>` operator always evaluates to `NULL`. -This includes `NULL = NULL` and `NULL <> NULL`. -To reliably test if a value is `NULL` use `v IS NULL` or `v IS NOT NULL` (the latter is equivalent to `NOT(v IS NULL)`). - -Other combinations of value types cannot be compared with each other. -For example, nodes, relationships, and literal maps cannot be compared to one another. -Comparing incomparable values will throw an error. - -[[ordering-and-comparison]] -=== Ordering and comparison of values - -xref:clauses/order-by.adoc[`ORDER BY`] requires that all values are orderable. -The following points explain how comparisons are made when using the `\<=`, `<`,`>=`, `>` operators. - -* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is `TRUE`). -* All comparability tests with `java.lang.Double.NaN` evaluate as `FALSE`. -For example, `1 > b` and `1 < b` are both `FALSE` when `b` is NaN. -* String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). -* Boolean values are compared for ordering such that `FALSE < TRUE`. -* When comparing values for ordering, if one of the arguments is `NULL`, the result is always `NULL`. -* xref:values-and-types/spatial.adoc[Spatial values] cannot be compared using the operators `\<=`, `<`,`>=`, `>`. -To compare spatial values within a specific range, use either the xref:functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] or the xref:functions/spatial.adoc#functions-point-wgs84-2d[`point()`] function. - -This section also includes the following categories: - -* xref:expressions/predicate-expressions/operators.adoc#value-hierarchy[Hierarchy of values] -* xref:expressions/predicate-expressions/operators.adoc#ordering-lists-maps[Ordering list and map values] -* xref:expressions/predicate-expressions/operators.adoc#ordering-spatial-temporal[Ordering spatial and temporal values] - -[[value-hierarchy]] -==== Hierarchy of values - -Values of different types are ordered based on a predefined hierarchy, from least to greatest, as outlined in the following list: - -* xref::values-and-types/maps.adoc#cypher-literal-maps[`MAP`] -* xref::values-and-types/property-structural-constructed.adoc#structural-types[`NODE`] -* xref::values-and-types/property-structural-constructed.adoc#structural-types[`RELATIONSHIP`] -* xref::values-and-types/lists.adoc[`LIST`] -* xref::patterns/fixed-length-patterns.adoc#path-patterns[`PATH`] -* xref::values-and-types/temporal.adoc[`ZONED DATETIME`] -* xref::values-and-types/temporal.adoc[`LOCAL DATETIME`] -* xref::values-and-types/temporal.adoc[`DATE`] -* xref::values-and-types/temporal.adoc[`ZONED TIME`] -* xref::values-and-types/temporal.adoc[`LOCAL TIME`] -* xref::values-and-types/temporal.adoc[`DURATION`] -* xref::values-and-types/spatial.adoc[`POINT`] -* xref::expressions/expressions-overview.adoc#string[`STRING`] -* xref::expressions/expressions-overview.adoc#boolean[`BOOLEAN`] -* Numbers: xref::expressions/expressions-overview.adoc#numerical[`INTEGER`, `FLOAT`] - -[NOTE] -`NULL` is ordered after all other values. - -.Sorting rules for mixed Cypher types -[source, cypher] ----- -WITH [42, "hello", NULL, true, {name: "Alice"}, [1, 2, 3], date("2024-02-10")] AS v -UNWIND v AS values -RETURN values -ORDER BY values ----- - -.Result -[role="queryresult",options="header,footer",cols="1* Constructed types]. - -[[ordering-spatial-temporal]] -==== Ordering spatial and temporal values - -The following applies to the ordering of xref:values-and-types/spatial.adoc[spatial types]: - -* `POINT` values are ordered after lists and before temporal types. -* `POINT` values of different coordinate reference systems (CRS) are ordered by the CRS code (the value of SRID field). -For the currently supported set of xref::values-and-types/spatial.adoc#cypher-spatial-crs[CRS], the following ascending order applies: `4326`, `4979`, `7302`, `9157`. -* `POINT` values with the same CRS are ordered by each coordinate value in turn; first `x`, then `y`, and finally `z`. -* Note that this ordering is different to the order returned by the spatial index, which follows the space filling curve. - -The following applies to the ordering of xref:values-and-types/temporal.adoc[temporal types]: - -* Temporal types are ordered after spatial types but before strings. -* Temporal values follow a chronological order. -For example, `2023-01-01` comes before `2024-01-01`. -* Temporal values are first sorted by type, then by value. -For example, `DATETIME` is considered "greater" than a `DATE` and `2023-02-10T12:00:00` comes before `2023-02-10T15:00:00` because it is chronologically earlier. -* Since there is no perfect way to compare duration values (because months and years have varying lengths), Cypher defines a specific rule for sorting them in `ORDER BY`: -** 1 year is treated as 365.2425 days (to account for leap years). -** 1 month is treated as 30.436875 days (which is 1/12 of a year). -** 1 day is always 24 hours. - -The following applies to the comparison of temporal types: - -* xref::values-and-types/temporal.adoc#cypher-temporal-instants[Temporal instant values] (like `DATETIME` and `DATE`) can be compared if they are of the same type. -An earlier instant is considered smaller (less than) compared to a later instant. -* Instants at the same point in time but with different time zones are not considered equal. -To ensure consistent ordering, Cypher sorts them first by their actual point in time. -If two instants have the same time but different time zones, they are ordered by their UTC offset (west to east, meaning negative offsets come first). -If they have the same time and offset but different named time zones, they are sorted alphabetically by the time zone name. -* Duration values cannot be directly compared. -Since the length of a day, month, or year varies, Cypher does not define a strict ordering for durations. -As a result, comparing two durations `(e.g, duration1 < duration2)` will always return `NULL`. - [[chaining-comparison-oeprators]] === Chaining comparison operators diff --git a/modules/ROOT/pages/values-and-types/index.adoc b/modules/ROOT/pages/values-and-types/index.adoc index 422140305..b1ff47c70 100644 --- a/modules/ROOT/pages/values-and-types/index.adoc +++ b/modules/ROOT/pages/values-and-types/index.adoc @@ -14,6 +14,7 @@ More information about the data values and types supported by Cypher can be foun * xref::values-and-types/lists.adoc[] * xref::values-and-types/maps.adoc[] * xref::values-and-types/casting-data.adoc[] +* xref:values-and-types/ordering-equality-comparison.adoc[] [TIP] For information about how to check the type of a value, see xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] diff --git a/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc new file mode 100644 index 000000000..96125a6e6 --- /dev/null +++ b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc @@ -0,0 +1,146 @@ += Equality, ordering, and comparison of value types +:description: Information about how Cypher compares and orders different value types, including equality and inequality rules. + +This page explains how Cypher compares and orders different value types, including equality and inequality rules. + +[[equality-and-comparison]] +== Equality and comparison of values + +The xref:expressions/predicate-expressions/operators.adoc#comparison-operators[equality (`=`) and inequality (`<>`) operators] allows for comparing the equality of different values. + +Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). + +Maps are only equal if they map exactly the same keys to equal values and lists are only equal if they contain the same sequence of equal values (e.g. `[3, 4] = [1+2, 8/2]`). + +Values of different types are considered as equal according to the following rules: + +* `PATH` values are treated as lists of alternating nodes and relationships and are equal to all lists that contain an identical sequence of nodes and relationships. +* Testing any value against `NULL` with either the `=` or `<>` operator always evaluates to `NULL`. +This includes `NULL = NULL` and `NULL <> NULL`. +To reliably test if a value is `NULL` use `v IS NULL` or `v IS NOT NULL` (the latter is equivalent to `NOT(v IS NULL)`). + +Other combinations of value types cannot be compared with each other. +For example, nodes, relationships, and literal maps cannot be compared to one another. +Comparing incomparable values will throw an error. + +[[ordering-and-comparison]] +== Ordering and comparison of values + +xref:clauses/order-by.adoc[`ORDER BY`] requires that all values are orderable. +The following points explain how comparisons are made when using the `\<=`, `<`,`>=`, `>` operators. + +* Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is `TRUE`). +* All comparability tests with `java.lang.Double.NaN` evaluate as `FALSE`. +For example, `1 > b` and `1 < b` are both `FALSE` when `b` is NaN. +* String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). +* Boolean values are compared for ordering such that `FALSE < TRUE`. +* When comparing values for ordering, if one of the arguments is `NULL`, the result is always `NULL`. +* xref:values-and-types/spatial.adoc[Spatial values] cannot be compared using the operators `\<=`, `<`,`>=`, `>`. +To compare spatial values within a specific range, use either the xref:functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] or the xref:functions/spatial.adoc#functions-point-wgs84-2d[`point()`] function. + +[[value-hierarchy]] +=== Hierarchy of values + +Values of different types are ordered based on a predefined hierarchy, from least to greatest, as outlined in the following list: + +* xref::values-and-types/maps.adoc#cypher-literal-maps[`MAP`] +* xref::values-and-types/property-structural-constructed.adoc#structural-types[`NODE`] +* xref::values-and-types/property-structural-constructed.adoc#structural-types[`RELATIONSHIP`] +* xref::values-and-types/lists.adoc[`LIST`] +* xref::patterns/fixed-length-patterns.adoc#path-patterns[`PATH`] +* xref::values-and-types/temporal.adoc[`ZONED DATETIME`] +* xref::values-and-types/temporal.adoc[`LOCAL DATETIME`] +* xref::values-and-types/temporal.adoc[`DATE`] +* xref::values-and-types/temporal.adoc[`ZONED TIME`] +* xref::values-and-types/temporal.adoc[`LOCAL TIME`] +* xref::values-and-types/temporal.adoc[`DURATION`] +* xref::values-and-types/spatial.adoc[`POINT`] +* xref::expressions/expressions-overview.adoc#string[`STRING`] +* xref::expressions/expressions-overview.adoc#boolean[`BOOLEAN`] +* Numbers: xref::expressions/expressions-overview.adoc#numerical[`INTEGER`, `FLOAT`] + +[NOTE] +`NULL` is ordered after all other values. + +.Sorting rules for mixed Cypher types +[source, cypher] +---- +WITH [42, "hello", NULL, true, {name: "Alice"}, [1, 2, 3], date("2024-02-10")] AS v +UNWIND v AS values +RETURN values +ORDER BY values +---- + +.Result +[role="queryresult",options="header,footer",cols="1* Constructed types]. + +[[ordering-spatial-temporal]] +=== Ordering spatial and temporal values + +The following applies to the ordering of xref:values-and-types/spatial.adoc[spatial types]: + +* `POINT` values are ordered after lists and before temporal types. +* `POINT` values of different coordinate reference systems (CRS) are ordered by the CRS code (the value of SRID field). +For the currently supported set of xref::values-and-types/spatial.adoc#cypher-spatial-crs[CRS], the following ascending order applies: `4326`, `4979`, `7302`, `9157`. +* `POINT` values with the same CRS are ordered by each coordinate value in turn; first `x`, then `y`, and finally `z`. +* Note that this ordering is different to the order returned by the spatial index, which follows the space filling curve. + +The following applies to the ordering of xref:values-and-types/temporal.adoc[temporal types]: + +* Temporal types are ordered after spatial types but before strings. +* Temporal values follow a chronological order. +For example, `2023-01-01` comes before `2024-01-01`. +* Temporal values are first sorted by type, then by value. +For example, `DATETIME` is considered "greater" than a `DATE` and `2023-02-10T12:00:00` comes before `2023-02-10T15:00:00` because it is chronologically earlier. +* Since there is no perfect way to compare duration values (because months and years have varying lengths), Cypher defines a specific rule for sorting them in `ORDER BY`: +** 1 year is treated as 365.2425 days (to account for leap years). +** 1 month is treated as 30.436875 days (which is 1/12 of a year). +** 1 day is always 24 hours. + +The following applies to the comparison of temporal types: + +* xref::values-and-types/temporal.adoc#cypher-temporal-instants[Temporal instant values] (like `DATETIME` and `DATE`) can be compared if they are of the same type. +An earlier instant is considered smaller (less than) compared to a later instant. +* Instants at the same point in time but with different time zones are not considered equal. +To ensure consistent ordering, Cypher sorts them first by their actual point in time. +If two instants have the same time but different time zones, they are ordered by their UTC offset (west to east, meaning negative offsets come first). +If they have the same time and offset but different named time zones, they are sorted alphabetically by the time zone name. +* Duration values cannot be directly compared. +Since the length of a day, month, or year varies, Cypher does not define a strict ordering for durations. +As a result, comparing two durations `(e.g, duration1 < duration2)` will always return `NULL`. From 2925cc4d5dbc8539481c399b97ed3bed8801e843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:39:45 +0100 Subject: [PATCH 28/32] change to predicates --- modules/ROOT/content-nav.adoc | 12 ++++++++---- .../gql-conformance/supported-mandatory.adoc | 4 ++-- .../gql-conformance/supported-optional.adoc | 8 ++++---- modules/ROOT/pages/clauses/where.adoc | 2 +- ...ations-additions-removals-compatibility.adoc | 8 ++++---- .../expressions/conditional-expressions.adoc | 2 +- .../pages/expressions/expressions-overview.adoc | 4 ++-- modules/ROOT/pages/expressions/index.adoc | 8 ++++---- .../predicate-expressions/index.adoc | 17 ----------------- .../pages/expressions/predicates/index.adoc | 17 +++++++++++++++++ .../operators.adoc | 8 ++++---- .../path-pattern-expressions.adoc | 2 +- .../type-predicate-expressions.adoc | 0 modules/ROOT/pages/functions/scalar.adoc | 2 +- .../using-indexes.adoc | 2 +- modules/ROOT/pages/syntax/parsing.adoc | 2 +- modules/ROOT/pages/values-and-types/index.adoc | 2 +- .../ordering-equality-comparison.adoc | 2 +- .../property-structural-constructed.adoc | 4 ++-- .../values-and-types/working-with-null.adoc | 2 +- 20 files changed, 56 insertions(+), 52 deletions(-) delete mode 100644 modules/ROOT/pages/expressions/predicate-expressions/index.adoc create mode 100644 modules/ROOT/pages/expressions/predicates/index.adoc rename modules/ROOT/pages/expressions/{predicate-expressions => predicates}/operators.adoc (96%) rename modules/ROOT/pages/expressions/{predicate-expressions => predicates}/path-pattern-expressions.adoc (97%) rename modules/ROOT/pages/expressions/{predicate-expressions => predicates}/type-predicate-expressions.adoc (100%) diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 6add790fe..927aac9f6 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -39,6 +39,10 @@ * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] +** xref:expressions/predicates/index.adoc[] +*** xref:expressions/predicates/operators.adoc[] +*** xref:expressions/predicates/path-pattern-expressions.adoc[] +*** xref:expressions/predicates/type-predicate-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] * xref:subqueries/index.adoc[] @@ -70,10 +74,10 @@ * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] -** xref:expressions/predicate-expressions/index.adoc[] -*** xref:expressions/predicate-expressions/operators.adoc[] -*** xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] -*** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] +** xref:expressions/predicates/index.adoc[] +*** xref:expressions/predicates/operators.adoc[] +*** xref:expressions/predicates/path-pattern-expressions.adoc[] +*** xref:expressions/predicates/type-predicate-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] * xref:functions/index.adoc[] diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc index 1bdf406dc..7ecb42f80 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-mandatory.adoc @@ -143,12 +143,12 @@ This is currently not available in Cypher. | 19.5 | -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-null[Type predicate expressions for `NULL`] +| xref:expressions/predicates/type-predicate-expressions.adoc#type-predicate-null[Type predicate expressions for `NULL`] | | 19.6 | -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#[] +| xref:expressions/predicates/type-predicate-expressions.adoc#[] | | 19.7 diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc index 9b96109e9..b64511105 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -85,7 +85,7 @@ These codes order the features in the table below. | GA06 | Value type predicates -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc[Type predicate expressions] +| xref:expressions/predicates/type-predicate-expressions.adoc[Type predicate expressions] | | GA07 @@ -211,12 +211,12 @@ GQL also defines a parameterless version of the function not in Cypher: `CURRENT | GV66 | Open dynamic unions -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] +| xref:expressions/predicates/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] | | GV67 | Closed dynamic unions -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-closed-dynamic-unions[Closed dynamic unions] +| xref:expressions/predicates/type-predicate-expressions.adoc#type-predicate-closed-dynamic-unions[Closed dynamic unions] | | GV70 @@ -226,7 +226,7 @@ GQL also defines a parameterless version of the function not in Cypher: `CURRENT | GV71 | Immaterial value types: empty type support (`NOTHING`)] -| xref:expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] +| xref:expressions/predicates/type-predicate-expressions.adoc#type-predicate-any-and-nothing[Type predicate expressions -> `ANY` and `NOTHING`] | |=== diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index ac9088fee..cf976e4b7 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -13,7 +13,7 @@ Both results and performance may be impacted if `WHERE` is put inside the wrong When used after `WITH`, `WHERE` simply filters the results. [TIP] -For more uses of `WHERE`, see xref:expressions/predicate-expressions/index.adoc[]. +For more uses of `WHERE`, see xref:expressions/predicates/index.adoc[]. [[where-example-graph]] == Example graph diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index 28547ebf0..78385b3cc 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -1424,7 +1424,7 @@ label:updated[] MATCH (n:Label) WHERE $param IS :: STRING NOT NULL AND n.prop = $param ---- -| `IS :: STRING NOT NULL` is now an xref:indexes/search-performance-indexes/using-indexes.adoc#text-indexes-type-predicate-expressions[index-compatible predicate]. +| `IS :: STRING NOT NULL` is now an xref:indexes/search-performance-indexes/using-indexes.adoc#text-indexes-type-predicates[index-compatible predicate]. |=== @@ -1710,7 +1710,7 @@ IS [NOT] :: ---- a| -Extended xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. +Extended xref:expressions/predicates/type-predicate-expressions.adoc[type predicate expressions]. Closed dynamic union types (`type1 \| type2 \| ...`) are now supported. For example, the following query which evaluates to true if a value is either of type `INTEGER` or `FLOAT`: [source, cypher, role="noheader"] @@ -1769,7 +1769,7 @@ IS [NOT] :: ---- a| -Extended xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. +Extended xref:expressions/predicates/type-predicate-expressions.adoc[type predicate expressions]. The newly supported types are: * `NOTHING` @@ -1943,7 +1943,7 @@ IS [NOT] :: ---- a| -Added xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. +Added xref:expressions/predicates/type-predicate-expressions.adoc[type predicate expressions]. The available types are: * `BOOLEAN` diff --git a/modules/ROOT/pages/expressions/conditional-expressions.adoc b/modules/ROOT/pages/expressions/conditional-expressions.adoc index 9d0bae2da..8a775bcb6 100644 --- a/modules/ROOT/pages/expressions/conditional-expressions.adoc +++ b/modules/ROOT/pages/expressions/conditional-expressions.adoc @@ -108,7 +108,7 @@ The supported comparators are: * xref::syntax/operators.adoc#query-operators-comparison[Regular Comparison Operators]: `+=+`, `+<>+`, `+<+`, `+>+`, `+<=+`, `+>=+` * xref:values-and-types/working-with-null.adoc#is-null-is-not-null[`IS NULL` Operator]: `IS [NOT] NULL` -* xref:expressions/predicate-expressions/type-predicate-expressions.adoc[Type Predicate Expression]: `IS [NOT] TYPED ` (Note that the form `IS [NOT] :: ` is not accepted) +* xref:expressions/predicates/type-predicate-expressions.adoc[Type Predicate Expression]: `IS [NOT] TYPED ` (Note that the form `IS [NOT] :: ` is not accepted) * xref::syntax/operators.adoc#match-string-is-normalized[Normalization Predicate Expression]: `IS [NOT] NORMALIZED` * xref::syntax/operators.adoc#query-operator-comparison-string-specific[String Comparison Operators]: `STARTS WITH`, `ENDS WITH`, `=~` (regex matching) diff --git a/modules/ROOT/pages/expressions/expressions-overview.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc index 3cd1188bd..ab1264cbd 100644 --- a/modules/ROOT/pages/expressions/expressions-overview.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -18,7 +18,7 @@ For more information, see xref:clauses/where.adoc#filter-on-dynamic-property[`WH * A xref:patterns/fixed-length-patterns.adoc#path-patterns[path-pattern]: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. * An xref:syntax/operators.adoc[operator application]: `1 + 2`, `3 < 4`. * A xref:subqueries/index.adoc[subquery expression]: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. -* A xref:expressions/predicate-expressions/operators#regular-expressions[regular expression]: `a.name =~ 'Tim.*'`. +* A xref:expressions/predicates/operators#regular-expressions[regular expression]: `a.name =~ 'Tim.*'`. * A xref:expressions/conditional-expressions.adoc[`CASE` expression]. * xref:values-and-types/working-with-null[`null`]. @@ -32,7 +32,7 @@ Learn more in link:https://neo4j.com/developer/kb/protecting-against-cypher-inje [NOTE] ==== Most expressions in Cypher evaluate to `null` if any of their inner expressions are `null`. -Notable exceptions are the operators `IS NULL`, `IS NOT NULL`, and the xref:expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions]. +Notable exceptions are the operators `IS NULL`, `IS NOT NULL`, and the xref:expressions/predicates/type-predicate-expressions.adoc[type predicate expressions]. ==== [[numerical]] diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index 99cca9ded..02f5a152e 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -4,8 +4,8 @@ A Cypher expression is any part of a query that evaluates to a value. For details and examples of specific expressions, see the following sections: * xref:expressions/expressions-overview.adoc[] -* xref:expressions/predicate-expressions/index.adoc[] -** xref:expressions/predicate-expressions/operators.adoc[] -** xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] -** xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] +* xref:expressions/predicates/index.adoc[] +** xref:expressions/predicates/operators.adoc[] +** xref:expressions/predicates/type-predicate-expressions.adoc[] +** xref:expressions/predicates/path-pattern-expressions.adoc[] * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc b/modules/ROOT/pages/expressions/predicate-expressions/index.adoc deleted file mode 100644 index 6622bcc90..000000000 --- a/modules/ROOT/pages/expressions/predicate-expressions/index.adoc +++ /dev/null @@ -1,17 +0,0 @@ -= Predicate expressions -:Description: Overview of the predicate expressions in Cypher. - -Predicate expressions evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`), and are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. - -This chapter is divided into the following sections: - -* xref:expressions/predicate-expressions/operators.adoc[] - information about boolean, comparison, string, and list operators. -* xref:expressions/predicate-expressions/path-pattern-expressions.adoc[] - Information about filtering queries with path pattern expressions. -* xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] - Information about how to verify the value type of a Cypher expression. - -The following can also serve as a predicate expressions (but are documented elsewhere): - -* xref:syntax/variables.adoc[] -* xref:queries/concepts.adoc[Properties] (See also xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]) -* xref:functions/predicate.adoc[Predicate functions]. -* xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicates/index.adoc b/modules/ROOT/pages/expressions/predicates/index.adoc new file mode 100644 index 000000000..88b526d43 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicates/index.adoc @@ -0,0 +1,17 @@ += Predicates +:Description: Overview of the predicate expressions in Cypher. + +Predicates evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`), and are frequently used for filtering in xref:clauses/where.adoc[`WHERE`] subclauses. + +This chapter is divided into the following sections: + +* xref:expressions/predicates/operators.adoc[]: information about boolean, comparison, string, and list operators. +* xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions. +* xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression. + +The following can also serve as a predicates if they result in a `BOOLEAN` value (but are documented elsewhere): + +* xref:syntax/variables.adoc[] +* xref:queries/concepts.adoc[Properties] (See also xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]) +* xref:functions/predicate.adoc[Predicate functions]. +* xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc b/modules/ROOT/pages/expressions/predicates/operators.adoc similarity index 96% rename from modules/ROOT/pages/expressions/predicate-expressions/operators.adoc rename to modules/ROOT/pages/expressions/predicates/operators.adoc index 38cc9cb52..8ab032ed2 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/operators.adoc +++ b/modules/ROOT/pages/expressions/predicates/operators.adoc @@ -5,10 +5,10 @@ Predicate operators are used to evaluate expressions and return `BOOLEAN` values. They are categorized as follows: -* xref:expressions/predicate-expressions/operators.adoc#boolean-operators[Boolean operators] (`AND`, `OR`, `XOR`, `NOT`) -* xref:expressions/predicate-expressions/operators.adoc#comparison-operators[Comparison operators] (`=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`) -* xref:expressions/predicate-expressions/operators.adoc#string-operators[String operators] (`=~`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`) -* xref:expressions/predicate-expressions/operators.adoc#list-operators[List operators] (`IN`) +* xref:expressions/predicates/operators.adoc#boolean-operators[Boolean operators] (`AND`, `OR`, `XOR`, `NOT`) +* xref:expressions/predicates/operators.adoc#comparison-operators[Comparison operators] (`=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`) +* xref:expressions/predicates/operators.adoc#string-operators[String operators] (`=~`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`) +* xref:expressions/predicates/operators.adoc#list-operators[List operators] (`IN`) [[where-example-graph]] == Example graph diff --git a/modules/ROOT/pages/expressions/predicate-expressions/path-pattern-expressions.adoc b/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc similarity index 97% rename from modules/ROOT/pages/expressions/predicate-expressions/path-pattern-expressions.adoc rename to modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc index c4eb467dc..8b4ffa557 100644 --- a/modules/ROOT/pages/expressions/predicate-expressions/path-pattern-expressions.adoc +++ b/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc @@ -83,7 +83,7 @@ RETURN employee.name AS employee; 1+d|Rows: 1 |=== -For more information about Cypher's boolean operators, see xref:expressions/predicate-expressions/operators.adoc#boolean-operators[Predicate expressions -> Boolean operators]. +For more information about Cypher's boolean operators, see xref:expressions/predicates/operators.adoc#boolean-operators[Predicate expressions -> Boolean operators]. Patterns can be placed inside expressions. diff --git a/modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc b/modules/ROOT/pages/expressions/predicates/type-predicate-expressions.adoc similarity index 100% rename from modules/ROOT/pages/expressions/predicate-expressions/type-predicate-expressions.adoc rename to modules/ROOT/pages/expressions/predicates/type-predicate-expressions.adoc diff --git a/modules/ROOT/pages/functions/scalar.adoc b/modules/ROOT/pages/functions/scalar.adoc index a3eec3799..918bbcaf1 100644 --- a/modules/ROOT/pages/functions/scalar.adoc +++ b/modules/ROOT/pages/functions/scalar.adoc @@ -1281,7 +1281,7 @@ The below list contains all currently supported types displayed by the `valueTyp This should be taken into account when relying on the output of the `valueType()` function. -See the xref::expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expression] for an alternative way of testing type values. +See the xref::expressions/predicates/type-predicate-expressions.adoc[type predicate expression] for an alternative way of testing type values. .+valueType()+ diff --git a/modules/ROOT/pages/indexes/search-performance-indexes/using-indexes.adoc b/modules/ROOT/pages/indexes/search-performance-indexes/using-indexes.adoc index cf7cdd732..517d99e81 100644 --- a/modules/ROOT/pages/indexes/search-performance-indexes/using-indexes.adoc +++ b/modules/ROOT/pages/indexes/search-performance-indexes/using-indexes.adoc @@ -849,7 +849,7 @@ Total database accesses: 186, total allocated memory: 472 This plan shows that the previously created range index on the `name` property is now used to solve the predicate. -[[text-indexes-type-predicate-expressions]] +[[text-indexes-type-predicates]] === Text indexes and type predicate expressions Text indexes require that predicates only include `STRING` properties. diff --git a/modules/ROOT/pages/syntax/parsing.adoc b/modules/ROOT/pages/syntax/parsing.adoc index 28a4947e5..d177197f7 100644 --- a/modules/ROOT/pages/syntax/parsing.adoc +++ b/modules/ROOT/pages/syntax/parsing.adoc @@ -25,7 +25,7 @@ Additional documentation on escaping rules for `STRING` literals, names and regu * xref::expressions/expressions-overview.adoc#expressions-string-literals[String literal escape sequences] * xref::syntax/naming.adoc#symbolic-names-escaping-rules[Using special characters in names] -* xref::expressions/predicate-expressions/operators.adoc#regular-expressions[Regular expressions] +* xref::expressions/predicates/operators.adoc#regular-expressions[Regular expressions] The Unicode version used by Cypher depends on the running JVM version. diff --git a/modules/ROOT/pages/values-and-types/index.adoc b/modules/ROOT/pages/values-and-types/index.adoc index b1ff47c70..16bbbaf43 100644 --- a/modules/ROOT/pages/values-and-types/index.adoc +++ b/modules/ROOT/pages/values-and-types/index.adoc @@ -17,4 +17,4 @@ More information about the data values and types supported by Cypher can be foun * xref:values-and-types/ordering-equality-comparison.adoc[] [TIP] -For information about how to check the type of a value, see xref:expressions/predicate-expressions/type-predicate-expressions.adoc[] +For information about how to check the type of a value, see xref:expressions/predicates/type-predicate-expressions.adoc[] diff --git a/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc index 96125a6e6..2a67a8d7c 100644 --- a/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc +++ b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc @@ -6,7 +6,7 @@ This page explains how Cypher compares and orders different value types, includi [[equality-and-comparison]] == Equality and comparison of values -The xref:expressions/predicate-expressions/operators.adoc#comparison-operators[equality (`=`) and inequality (`<>`) operators] allows for comparing the equality of different values. +The xref:expressions/predicates/operators.adoc#comparison-operators[equality (`=`) and inequality (`<>`) operators] allows for comparing the equality of different values. Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). diff --git a/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc b/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc index bc304b2e9..98f6c6240 100644 --- a/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc +++ b/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc @@ -74,7 +74,7 @@ For more details, see xref::values-and-types/working-with-null.adoc[working with The table below shows the types and their syntactic synonyms. -These types (and their synonyms) can be used in xref::expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions] and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. +These types (and their synonyms) can be used in xref::expressions/predicates/type-predicate-expressions.adoc[type predicate expressions] and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. They are also returned as a `STRING` value when using the xref::functions/scalar.adoc#functions-valueType[valueType()] function. However, not all types can be used in all places. @@ -121,7 +121,7 @@ The type `PROPERTY VALUE` is expanded to a closed dynamic union of all valid pro For example, given the closed dynamic type `BOOL | LIST | BOOLEAN | LIST`, the normalized type would be: `BOOLEAN | LIST`. -This normalization is run on types used in xref::expressions/predicate-expressions/type-predicate-expressions.adoc[type predicate expressions], and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. +This normalization is run on types used in xref::expressions/predicates/type-predicate-expressions.adoc[type predicate expressions], and in xref::constraints/managing-constraints.adoc#create-property-type-constraints[property type constraints]. Type normalization is also used to ensure the consistency of the output for the xref::functions/scalar.adoc#functions-valueType[valueType()] function. [[ordering-of-types]] diff --git a/modules/ROOT/pages/values-and-types/working-with-null.adoc b/modules/ROOT/pages/values-and-types/working-with-null.adoc index df7a68920..e980d0494 100644 --- a/modules/ROOT/pages/values-and-types/working-with-null.adoc +++ b/modules/ROOT/pages/values-and-types/working-with-null.adoc @@ -5,7 +5,7 @@ In Cypher, `null` is used to represent missing or undefined values. All data types in Cypher are nullable. -This means that xref::expressions/predicate-expressions/type-predicate-expressions.adoc#type-predicate-null[type predicate expressions] always return `true` for `null` values. +This means that xref::expressions/predicates/type-predicate-expressions.adoc#type-predicate-null[type predicate expressions] always return `true` for `null` values. Conceptually, `null` means **a missing or unknown value**, and it is treated somewhat differently from other values. For example, returning a property from a node that does not have said property produces `null`. From 50ba63991eef2ca562d5b978f99aec1d38a73a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:56:10 +0100 Subject: [PATCH 29/32] post review additions and corrections --- modules/ROOT/content-nav.adoc | 5 +- .../predicates/boolean-operators.adoc | 154 +++++++++ .../predicates/comparison-operators.adoc | 238 ++++++++++++++ .../pages/expressions/predicates/index.adoc | 7 +- .../predicates/list-operators.adoc | 253 +++++++++++++++ .../expressions/predicates/operators.adoc | 13 +- .../predicates/path-pattern-expressions.adoc | 7 +- .../predicates/string-operators.adoc | 306 ++++++++++++++++++ 8 files changed, 969 insertions(+), 14 deletions(-) create mode 100644 modules/ROOT/pages/expressions/predicates/boolean-operators.adoc create mode 100644 modules/ROOT/pages/expressions/predicates/comparison-operators.adoc create mode 100644 modules/ROOT/pages/expressions/predicates/list-operators.adoc create mode 100644 modules/ROOT/pages/expressions/predicates/string-operators.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 927aac9f6..f2f664f49 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -40,7 +40,10 @@ * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] ** xref:expressions/predicates/index.adoc[] -*** xref:expressions/predicates/operators.adoc[] +*** xref:expressions/predicates/boolean-operators.adoc[] +*** xref:expressions/predicates/comparison-operators.adoc[] +*** xref:expressions/predicates/string-operators.adoc[] +*** xref:expressions/predicates/list-operators.adoc[] *** xref:expressions/predicates/path-pattern-expressions.adoc[] *** xref:expressions/predicates/type-predicate-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicates/boolean-operators.adoc b/modules/ROOT/pages/expressions/predicates/boolean-operators.adoc new file mode 100644 index 000000000..14b614313 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicates/boolean-operators.adoc @@ -0,0 +1,154 @@ += Boolean operators +:description: Information about Cypher's boolean operators. +:table-caption!: + +Boolean operators are used to combine or evaluate logical conditions. +Cypher contains the following boolean operators: + +* Conjunction: `AND` +* Disjunction: `OR` +* Exclusive disjunction: `XOR` +* Negation: `NOT` + +.Truth table for boolean operators +[options="header", cols="^,^,^,^,^,^", width="85%"] +|=== +|a | b | a `AND` b | a `OR` b | a `XOR` b | `NOT` a +|`FALSE` | `FALSE` | `FALSE` | `FALSE` | `FALSE` | `TRUE` +|`FALSE` | `NULL` | `FALSE` | `NULL` | `NULL` | `TRUE` +|`FALSE` | `TRUE` | `FALSE` | `TRUE` | `TRUE` | `TRUE` +|`TRUE` | `FALSE` | `FALSE` | `TRUE` | `TRUE` | `FALSE` +|`TRUE` | `NULL` | `NULL` | `TRUE` | `NULL` | `FALSE` +|`TRUE` | `TRUE` | `TRUE` | `TRUE` | `FALSE` | `FALSE` +|`NULL` | `FALSE` | `FALSE` | `NULL` | `NULL` | `NULL` +|`NULL` | `NULL` | `NULL` | `NULL` | `NULL` | `NULL` +|`NULL` | `TRUE` | `NULL` | `TRUE` | `NULL` | `NULL` +|=== + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::predicate_operators.svg[width="500",role="middle"] + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) +---- + +== Examples + +.Boolean operators +===== + +.`AND` operator +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age > 30 AND n.role = 'Software developer' +RETURN n.name AS name, n.age AS age, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 30 XOR n.role = 'Software developer' +RETURN n.name AS name, n.age AS age, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="5* 60 AND n.role = 'Security engineer') OR NOT (n.role = 'Director' OR n.name = 'Eskil') +RETURN n.name AS name, n.age AS age, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="3*` +* Less than: `<` +* Greater than: `>` +* Less than or equal to: `\<=` +* Greater than or equal to: `>=` +* `IS NULL` +* `IS NOT NULL` + +[TIP] +For more information about how Cypher orders and compares different value types, see xref:values-and-types/ordering-equality-comparison.adoc[Values and types -> Equality, ordering, and comparison of value types] + + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::predicate_operators.svg[width="500",role="middle"] + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) +---- + +[[examples]] +== Examples + +.Comparison operators +===== + +.Equality operator (`=`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.role = 'Software developer' +RETURN n.name AS name, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.role <> 'Software developer' +RETURN n.name AS name, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age > 39 +RETURN n.name AS name, n.age AS age +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +[source, cypher] +---- +MATCH (n:Person) +WHERE n.age => 39 +RETURN n.name AS name, n.age AS age +---- + +.Result +[role="queryresult",options="header,footer",cols="2* z`, `x` and `z` are not compared. + +[[chaining-equality-operators]] +=== Chaining equality operators + +Chains of `=` and `<>` are treated in a special way in Cypher. +Specifically, `1=1=true` is equivalent to `1=1 AND 1=true` and not to `(1=1)=true` or `1=(1=true)`. +For example, the following expressions are equivalent. + +.Equivalent expressions +[source, syntax, role=noplay] +---- +a < b = c <= d <> e; +a < b AND b = c AND c <= d AND d <> e +---- diff --git a/modules/ROOT/pages/expressions/predicates/index.adoc b/modules/ROOT/pages/expressions/predicates/index.adoc index 88b526d43..08cd4fe77 100644 --- a/modules/ROOT/pages/expressions/predicates/index.adoc +++ b/modules/ROOT/pages/expressions/predicates/index.adoc @@ -5,7 +5,10 @@ Predicates evaluate to a `BOOLEAN` value (`TRUE`, `FALSE`, or `NULL`), and are f This chapter is divided into the following sections: -* xref:expressions/predicates/operators.adoc[]: information about boolean, comparison, string, and list operators. +* xref:expressions/predicates/boolean-operators.adoc[]: `AND`, `OR`, `XOR`, `NOT` +* xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL` +* xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` +* xref:expressions/predicates/list-operators.adoc[]: `IN` * xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions. * xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression. @@ -13,5 +16,5 @@ The following can also serve as a predicates if they result in a `BOOLEAN` value * xref:syntax/variables.adoc[] * xref:queries/concepts.adoc[Properties] (See also xref:values-and-types/property-structural-constructed.adoc#property-types[Values and types -> property types]) -* xref:functions/predicate.adoc[Predicate functions]. +* xref:functions/predicate.adoc[Predicate functions] * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicates/list-operators.adoc b/modules/ROOT/pages/expressions/predicates/list-operators.adoc new file mode 100644 index 000000000..11648cb44 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicates/list-operators.adoc @@ -0,0 +1,253 @@ += List operators +:description: Information about Cypher's list operators. +:table-caption!: + +List operators are used to perform operations on xref:values-and-types/lists.adoc[`LIST`] values. +Cypher contains the following list operator: + +* Membership: `IN` + + +For additional list predicates, see: + +* The following quantifier predicates: xref:functions/predicate.adoc#functions-all[`all()`], xref:functions/predicate.adoc#functions-any[`any()`], xref:functions/predicate.adoc#functions-none[`none()`], and xref:functions/predicate.adoc#functions-single[`single()`]. +* The xref:functions/predicate.adoc#functions-isempty[`isEmpty()`] function. + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::predicate_operators.svg[width="500",role="middle"] + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) +---- + +== Examples + +.Basic `LIST` membership checks +===== + +.`IN` operator +[source, cypher] +---- +MATCH (n:Person) +WHERE n.role IN ['Software developer', 'Project manager'] +RETURN n.name AS name, n.role AS role +---- + +.Result +[role="queryresult",options="header,footer",cols="2*`) +.Greater than or equal operator (`\=>`) [source, cypher] ---- MATCH (n:Person) -WHERE n.age > 39 +WHERE n.age => 39 RETURN n.name AS name, n.age AS age ---- @@ -585,7 +584,7 @@ For more information, see the documentation for link:https://unicode.org/reports .`IS NORMALIZED` operator [source, cypher] ---- -RETURN "the \u212B char" IS NORMALIZED AS normalized +RETURN 'the \u212B char' IS NORMALIZED AS normalized ---- .Result @@ -609,7 +608,7 @@ The `IS NOT NORMALIZED` operator is used to check whether the given `STRING` is .`IS NOT NORMALIZED` [source, cypher] ---- -RETURN "the \u212B char" IS NOT NORMALIZED AS notNormalized +RETURN 'the \u212B char' IS NOT NORMALIZED AS notNormalized ---- .Result @@ -642,7 +641,7 @@ The available normalization types are: .Query [source, cypher] ---- -WITH "the \u00E4 char" as myString +WITH 'the \u00E4 char' as myString RETURN myString IS NFC NORMALIZED AS nfcNormalized, myString IS NFD NORMALIZED AS nfdNormalized ---- diff --git a/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc b/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc index 8b4ffa557..46c89c4a2 100644 --- a/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc +++ b/modules/ROOT/pages/expressions/predicates/path-pattern-expressions.adoc @@ -47,9 +47,8 @@ CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager'}), .Simple path pattern expression [source, cypher] ---- -MATCH (manager:Person {name: 'Alice'}), - (employee:Person) -WHERE (employee)-[:WORKS_FOR]->(manager) +MATCH (employee:Person) +WHERE (employee)-[:WORKS_FOR]->(:Person {name: 'Alice'}) RETURN employee.name AS employee ---- @@ -69,7 +68,7 @@ RETURN employee.name AS employee ---- MATCH (employee:Person) WHERE NOT employee.name = 'Cecil' AND (employee)-[:WORKS_FOR]->(:Person {name: 'Alice'}) -RETURN employee.name AS employee; +RETURN employee.name AS employee ---- diff --git a/modules/ROOT/pages/expressions/predicates/string-operators.adoc b/modules/ROOT/pages/expressions/predicates/string-operators.adoc new file mode 100644 index 000000000..f7164b1f5 --- /dev/null +++ b/modules/ROOT/pages/expressions/predicates/string-operators.adoc @@ -0,0 +1,306 @@ += String operators +:description: Information about Cypher's predicate string operators. +:table-caption!: + +String operators are used to perform operations on `STRING` values. +Cypher contains the following string operators: + +* Prefix: `STARTS WITH` (case sensitive) +* Suffix: `ENDS WITH` (case sensitive) +* substring: `CONTAINS` (case sensitive) +* Regular expression: `=~` +* `IS NORMALIZED` +* `IS NOT NORMALIZED` + +These operators perform _case-sensitive_ matching. +Attempting to use them on values which are not `STRING` values will return `NULL`. + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::predicate_operators.svg[width="500",role="middle"] + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) +---- + +== Examples + +.Prefix, suffix, and substring operators +===== + +.`STARTS WITH` operator +[source, cypher] +---- +MATCH (n:Person) +WHERE n.name STARTS WITH 'C' +RETURN n.name AS name +---- + + +.Result +[role="queryresult",options="header,footer",cols="1* Date: Wed, 26 Feb 2025 12:55:16 +0100 Subject: [PATCH 30/32] fix list examples --- modules/ROOT/pages/expressions/index.adoc | 9 ++++++--- .../expressions/predicates/list-operators.adoc | 16 +++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index 02f5a152e..d3492ea0c 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -5,7 +5,10 @@ For details and examples of specific expressions, see the following sections: * xref:expressions/expressions-overview.adoc[] * xref:expressions/predicates/index.adoc[] -** xref:expressions/predicates/operators.adoc[] -** xref:expressions/predicates/type-predicate-expressions.adoc[] -** xref:expressions/predicates/path-pattern-expressions.adoc[] +** xref:expressions/predicates/boolean-operators.adoc[]: `AND`, `OR`, `XOR`, `NOT` +** xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL` +** xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` +** xref:expressions/predicates/list-operators.adoc[]: `IN` +** xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions. +** xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression. * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicates/list-operators.adoc b/modules/ROOT/pages/expressions/predicates/list-operators.adoc index 11648cb44..48492b06b 100644 --- a/modules/ROOT/pages/expressions/predicates/list-operators.adoc +++ b/modules/ROOT/pages/expressions/predicates/list-operators.adoc @@ -7,10 +7,9 @@ Cypher contains the following list operator: * Membership: `IN` - For additional list predicates, see: -* The following quantifier predicates: xref:functions/predicate.adoc#functions-all[`all()`], xref:functions/predicate.adoc#functions-any[`any()`], xref:functions/predicate.adoc#functions-none[`none()`], and xref:functions/predicate.adoc#functions-single[`single()`]. +* The following predicate functions: xref:functions/predicate.adoc#functions-all[`all()`], xref:functions/predicate.adoc#functions-any[`any()`], xref:functions/predicate.adoc#functions-none[`none()`], and xref:functions/predicate.adoc#functions-single[`single()`]. * The xref:functions/predicate.adoc#functions-isempty[`isEmpty()`] function. [[example-graph]] @@ -128,7 +127,7 @@ RETURN NULL IN [1, 2, NULL] AS nullInList, 123 IN NULL AS valueInNull 2+d|Rows: 1 |=== -To check if `NULL` is a member of a `LIST`, use a xref: functions/predicate.adoc[predicate function] such as xref:functions/predicate.adoc#functions-any[`any()`]: +To check if `NULL` is a member of a `LIST`, use the xref:functions/predicate.adoc#functions-any[`any()`] function: .Checking if `NULL` is a member of a `LIST` [source, cypher] @@ -150,10 +149,9 @@ RETURN any(x IN [1, 2, NULL] WHERE x IS NULL) AS containsNull == Nested lists When used with nested `LIST` values, the `IN` operator evaluates whether a `LIST` is an exact match to any of the nested `LIST` values that are part of an outer `LIST`. -The operator checks for exact matches, meaning that it will only return `TRUE` when the entire nested `LIST` matches one of the `LIST` values within the outer `LIST`. -Partial matches or individual elements within a nested `LIST` will return `FALSE`. +Partial matches of individual elements within a nested `LIST` will return `FALSE`. -.Nested `LIST` values +.Checking for membership in nested `LIST` values ===== .Checking if a `LIST` is in a `LIST` of nested `LIST` values @@ -211,10 +209,10 @@ RETURN [1] IN [[1, 2], [3, 4]] AS listInNestedTest == List subsets A subset check verifies if all elements of one `LIST` exist in another. -Set checks ignores `LIST` order and groupings, and does not require exact sublist matches. +In other words, it ignores `LIST` order and groupings. The xref:functions/predicate.adoc#functions-all[`all()`] function is used to ensure that every element in the first `LIST` is found in the second `LIST`. -.Sublist check +.Subset check [source, cypher] ---- WITH [1,3,4] AS sub, [3,5,1,7,6,2,8,4] AS list @@ -233,7 +231,7 @@ This returns `TRUE` because all elements in `sub` are part of `list`. 1+d|Rows: 1 |=== -.Sublist check +.Subset check [source, cypher] ---- WITH [1,3,9] AS sub, [3,5,1,7,6,2,8,4] AS list From 1e75deb1389d631155ae090bbfc18b59bd272199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:09:47 +0100 Subject: [PATCH 31/32] clean up --- modules/ROOT/content-nav.adoc | 2 +- modules/ROOT/pages/expressions/index.adoc | 2 +- modules/ROOT/pages/expressions/predicates/index.adoc | 2 +- .../pages/expressions/predicates/list-operators.adoc | 11 +++-------- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index f2f664f49..25c6cd416 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -42,8 +42,8 @@ ** xref:expressions/predicates/index.adoc[] *** xref:expressions/predicates/boolean-operators.adoc[] *** xref:expressions/predicates/comparison-operators.adoc[] -*** xref:expressions/predicates/string-operators.adoc[] *** xref:expressions/predicates/list-operators.adoc[] +*** xref:expressions/predicates/string-operators.adoc[] *** xref:expressions/predicates/path-pattern-expressions.adoc[] *** xref:expressions/predicates/type-predicate-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index d3492ea0c..1b1812a3a 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -7,8 +7,8 @@ For details and examples of specific expressions, see the following sections: * xref:expressions/predicates/index.adoc[] ** xref:expressions/predicates/boolean-operators.adoc[]: `AND`, `OR`, `XOR`, `NOT` ** xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL` -** xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` ** xref:expressions/predicates/list-operators.adoc[]: `IN` +** xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` ** xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions. ** xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression. * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/predicates/index.adoc b/modules/ROOT/pages/expressions/predicates/index.adoc index 08cd4fe77..7ca49610d 100644 --- a/modules/ROOT/pages/expressions/predicates/index.adoc +++ b/modules/ROOT/pages/expressions/predicates/index.adoc @@ -7,8 +7,8 @@ This chapter is divided into the following sections: * xref:expressions/predicates/boolean-operators.adoc[]: `AND`, `OR`, `XOR`, `NOT` * xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL` -* xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` * xref:expressions/predicates/list-operators.adoc[]: `IN` +* xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~` * xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions. * xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression. diff --git a/modules/ROOT/pages/expressions/predicates/list-operators.adoc b/modules/ROOT/pages/expressions/predicates/list-operators.adoc index 48492b06b..fb084bc5f 100644 --- a/modules/ROOT/pages/expressions/predicates/list-operators.adoc +++ b/modules/ROOT/pages/expressions/predicates/list-operators.adoc @@ -152,9 +152,6 @@ When used with nested `LIST` values, the `IN` operator evaluates whether a `LIST Partial matches of individual elements within a nested `LIST` will return `FALSE`. .Checking for membership in nested `LIST` values -===== - -.Checking if a `LIST` is in a `LIST` of nested `LIST` values [source, cypher] ---- RETURN [0, 2] IN [[1, 2], [3, 4]] AS listInNestedList @@ -171,7 +168,7 @@ RETURN [0, 2] IN [[1, 2], [3, 4]] AS listInNestedList |=== -.Checking if a `LIST` is in a `LIST` of nested `LIST` values +.Checking for membership in nested `LIST` values [source, cypher] ---- RETURN [3, 4] IN [[1, 2], [3, 4]] AS listInNestedList @@ -188,7 +185,7 @@ RETURN [3, 4] IN [[1, 2], [3, 4]] AS listInNestedList |=== -.Checking partial match in nested `LIST` values +.Checking for partial membership in nested `LIST` values [source, cypher] ---- RETURN [1] IN [[1, 2], [3, 4]] AS listInNestedTest @@ -204,12 +201,10 @@ RETURN [1] IN [[1, 2], [3, 4]] AS listInNestedTest 1+d|Rows: 1 |=== -===== - +[[list-subsets]] == List subsets A subset check verifies if all elements of one `LIST` exist in another. -In other words, it ignores `LIST` order and groupings. The xref:functions/predicate.adoc#functions-all[`all()`] function is used to ensure that every element in the first `LIST` is found in the second `LIST`. .Subset check From bc5ae2e45f5a999a0482e43cc414dffea1f2b18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 27 Feb 2025 08:55:48 +0100 Subject: [PATCH 32/32] add example and clean up --- modules/ROOT/content-nav.adoc | 19 +- .../expressions/expressions-overview.adoc | 2 +- .../predicates/list-operators.adoc | 27 + .../expressions/predicates/operators.adoc | 694 ------------------ .../predicates/path-pattern-expressions.adoc | 2 +- modules/ROOT/pages/syntax/parsing.adoc | 2 +- .../ordering-equality-comparison.adoc | 2 +- 7 files changed, 35 insertions(+), 713 deletions(-) delete mode 100644 modules/ROOT/pages/expressions/predicates/operators.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 25c6cd416..0386481a3 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -37,17 +37,6 @@ ** xref:clauses/where.adoc[] ** xref:clauses/with.adoc[] -* xref:expressions/index.adoc[] -** xref:expressions/expressions-overview.adoc[] -** xref:expressions/predicates/index.adoc[] -*** xref:expressions/predicates/boolean-operators.adoc[] -*** xref:expressions/predicates/comparison-operators.adoc[] -*** xref:expressions/predicates/list-operators.adoc[] -*** xref:expressions/predicates/string-operators.adoc[] -*** xref:expressions/predicates/path-pattern-expressions.adoc[] -*** xref:expressions/predicates/type-predicate-expressions.adoc[] -** xref:expressions/conditional-expressions.adoc[] - * xref:subqueries/index.adoc[] ** xref:subqueries/call-subquery.adoc[] ** xref:subqueries/subqueries-in-transactions.adoc[] @@ -63,7 +52,6 @@ ** xref:patterns/non-linear-patterns.adoc[] ** xref:patterns/reference.adoc[] - * xref:values-and-types/index.adoc[] ** xref:values-and-types/property-structural-constructed.adoc[] ** xref:values-and-types/temporal.adoc[] @@ -74,11 +62,13 @@ ** xref:values-and-types/casting-data.adoc[] ** xref:values-and-types/ordering-equality-comparison.adoc[] - * xref:expressions/index.adoc[] ** xref:expressions/expressions-overview.adoc[] ** xref:expressions/predicates/index.adoc[] -*** xref:expressions/predicates/operators.adoc[] +*** xref:expressions/predicates/boolean-operators.adoc[] +*** xref:expressions/predicates/comparison-operators.adoc[] +*** xref:expressions/predicates/list-operators.adoc[] +*** xref:expressions/predicates/string-operators.adoc[] *** xref:expressions/predicates/path-pattern-expressions.adoc[] *** xref:expressions/predicates/type-predicate-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] @@ -101,7 +91,6 @@ ** xref:functions/user-defined.adoc[] ** xref:functions/vector.adoc[] - * xref:genai-integrations.adoc[] * xref:indexes/index.adoc[] ** xref:indexes/search-performance-indexes/overview.adoc[] diff --git a/modules/ROOT/pages/expressions/expressions-overview.adoc b/modules/ROOT/pages/expressions/expressions-overview.adoc index ab1264cbd..94251910c 100644 --- a/modules/ROOT/pages/expressions/expressions-overview.adoc +++ b/modules/ROOT/pages/expressions/expressions-overview.adoc @@ -18,7 +18,7 @@ For more information, see xref:clauses/where.adoc#filter-on-dynamic-property[`WH * A xref:patterns/fixed-length-patterns.adoc#path-patterns[path-pattern]: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. * An xref:syntax/operators.adoc[operator application]: `1 + 2`, `3 < 4`. * A xref:subqueries/index.adoc[subquery expression]: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. -* A xref:expressions/predicates/operators#regular-expressions[regular expression]: `a.name =~ 'Tim.*'`. +* A xref:expressions/predicates/string-operators#regular-expressions[regular expression]: `a.name =~ 'Tim.*'`. * A xref:expressions/conditional-expressions.adoc[`CASE` expression]. * xref:values-and-types/working-with-null[`null`]. diff --git a/modules/ROOT/pages/expressions/predicates/list-operators.adoc b/modules/ROOT/pages/expressions/predicates/list-operators.adoc index fb084bc5f..0f1d9365d 100644 --- a/modules/ROOT/pages/expressions/predicates/list-operators.adoc +++ b/modules/ROOT/pages/expressions/predicates/list-operators.adoc @@ -77,6 +77,33 @@ RETURN p.name AS name, p.role AS role 2+d|Rows: 3 |=== +The below query finds `Person` nodes that share the `role` of `Cecil` or `Eskil` but have a different `name`. + +.Multiple `IN` operators +[source, cypher] +---- +WITH ['Cecil', 'Eskil'] AS names +MATCH (ce:Person) +WHERE ce.name IN names +WITH collect(ce.role) AS roles, names +MATCH (p:Person) +WHERE p.role IN roles AND NOT p.name IN names +RETURN p.name AS name, p.role AS role +---- + +Only `Cecilia` is returned because she shares a `role` with `Cecil` (no `Person` node in the graph shares a `role` with `Eskil`). + +.Result +[role="queryresult",options="header,footer",cols="2*`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`) -* xref:expressions/predicates/operators.adoc#string-operators[String operators] (`=~`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`) -* xref:expressions/predicates/operators.adoc#list-operators[List operators] (`IN`) - -== Example graph - -The following graph is used for the examples below: - -image::predicate_operators.svg[width="500",role="middle"] - -To recreate the graph, run the following query in an empty Neo4j database: - -[source, cypher, role=test-setup] ----- -CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', email: 'alice@company.com'}), - (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', email: 'cecil@private.se'}), - (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer'}), - (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer'}), - (daniel:Person {name: 'Daniel', age: 39, role: 'Director', email: 'daniel@company.com'}), - (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', email: 'eskil@company.com'}) ----- - -[[boolean-operators]] -== Boolean operators - -The boolean operators comprise: - -* Conjunction: `AND` -* Disjunction: `OR` -* Exclusive disjunction: `XOR` -* Negation: `NOT` - -.Truth table for boolean operators -[options="header", cols="^,^,^,^,^,^", width="85%"] -|=== -|a | b | a `AND` b | a `OR` b | a `XOR` b | `NOT` a -|`FALSE` | `FALSE` | `FALSE` | `FALSE` | `FALSE` | `TRUE` -|`FALSE` | `NULL` | `FALSE` | `NULL` | `NULL` | `TRUE` -|`FALSE` | `TRUE` | `FALSE` | `TRUE` | `TRUE` | `TRUE` -|`TRUE` | `FALSE` | `FALSE` | `TRUE` | `TRUE` | `FALSE` -|`TRUE` | `NULL` | `NULL` | `TRUE` | `NULL` | `FALSE` -|`TRUE` | `TRUE` | `TRUE` | `TRUE` | `FALSE` | `FALSE` -|`NULL` | `FALSE` | `FALSE` | `NULL` | `NULL` | `NULL` -|`NULL` | `NULL` | `NULL` | `NULL` | `NULL` | `NULL` -|`NULL` | `TRUE` | `NULL` | `TRUE` | `NULL` | `NULL` -|=== - -.Boolean operators -===== - -.`AND` operator -[source, cypher] ----- -MATCH (n:Person) -WHERE n.age > 30 AND n.role = 'Software developer' -RETURN n.name AS name, n.age AS age, n.role AS role ----- - -.Result -[role="queryresult",options="header,footer",cols="3* 30 XOR n.role = 'Software developer' -RETURN n.name AS name, n.age AS age, n.role AS role ----- - -.Result -[role="queryresult",options="header,footer",cols="5* 60 AND n.role = 'Security engineer') OR NOT (n.role = 'Director' OR n.name = 'Eskil') -RETURN n.name AS name, n.age AS age, n.role AS role ----- - -.Result -[role="queryresult",options="header,footer",cols="3*` -* Less than: `<` -* Greater than: `>` -* Less than or equal to: `\<=` -* Greater than or equal to: `>=` -* `IS NULL` -* `IS NOT NULL` - -[TIP] -For more information about how Cypher orders and compares different value types, see xref:values-and-types/ordering-equality-comparison.adoc[Values and types -> Equality, ordering, and comparison of value types] - -.Comparison operators -===== - -.Equality operator (`=`) -[source, cypher] ----- -MATCH (n:Person) -WHERE n.role = 'Software developer' -RETURN n.name AS name, n.role AS role ----- - -.Result -[role="queryresult",options="header,footer",cols="2*`) -[source, cypher] ----- -MATCH (n:Person) -WHERE n.role <> 'Software developer' -RETURN n.name AS name, n.role AS role ----- - -.Result -[role="queryresult",options="header,footer",cols="2*`) -[source, cypher] ----- -MATCH (n:Person) -WHERE n.age > 39 -RETURN n.name AS name, n.age AS age ----- - -.Result -[role="queryresult",options="header,footer",cols="2*`) -[source, cypher] ----- -MATCH (n:Person) -WHERE n.age => 39 -RETURN n.name AS name, n.age AS age ----- - -.Result -[role="queryresult",options="header,footer",cols="2* z`, `x` and `z` are not compared. - -[[chaining-equality-operators]] -==== Chaining equality operators - -Chains of `=` and `<>` are treated in a special way in Cypher. -Specifically, `1=1=true` is equivalent to `1=1 AND 1=true` and not to `(1=1)=true` or `1=(1=true)`. -For example, the following expressions are equivalent. - -.Equivalent expressions -[source, syntax, role=noplay] ----- -a < b = c <= d <> e; -a < b AND b = c AND c <= d AND d <> e ----- - -[[string-operators]] -== String operators - -The string operators comprise: - -* Prefix: `STARTS WITH` (case sensitive) -* Suffix: `ENDS WITH` (case sensitive) -* substring: `CONTAINS` (case sensitive) -* Regular expression: `=~` -* `IS NORMALIZED` -* `IS NOT NORMALIZED` - -These operators perform _case-sensitive_ matching. -Attempting to use them on values which are not `STRING` values will return `NULL`. - -.Prefix, suffix, and substring operators -===== - -.`STARTS WITH` operator -[source, cypher] ----- -MATCH (n:Person) -WHERE n.name STARTS WITH 'C' -RETURN n.name AS name ----- - - -.Result -[role="queryresult",options="header,footer",cols="1* Boolean operators]. +For more information about Cypher's boolean operators, see xref:expressions/predicates/boolean-operators.adoc[Predicate expressions -> Boolean operators]. Patterns can be placed inside expressions. diff --git a/modules/ROOT/pages/syntax/parsing.adoc b/modules/ROOT/pages/syntax/parsing.adoc index d177197f7..b35919f00 100644 --- a/modules/ROOT/pages/syntax/parsing.adoc +++ b/modules/ROOT/pages/syntax/parsing.adoc @@ -25,7 +25,7 @@ Additional documentation on escaping rules for `STRING` literals, names and regu * xref::expressions/expressions-overview.adoc#expressions-string-literals[String literal escape sequences] * xref::syntax/naming.adoc#symbolic-names-escaping-rules[Using special characters in names] -* xref::expressions/predicates/operators.adoc#regular-expressions[Regular expressions] +* xref:expressions/predicates/string-operators.adoc#regular-expressions[Regular expressions] The Unicode version used by Cypher depends on the running JVM version. diff --git a/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc index 2a67a8d7c..f5cfb600d 100644 --- a/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc +++ b/modules/ROOT/pages/values-and-types/ordering-equality-comparison.adoc @@ -6,7 +6,7 @@ This page explains how Cypher compares and orders different value types, includi [[equality-and-comparison]] == Equality and comparison of values -The xref:expressions/predicates/operators.adoc#comparison-operators[equality (`=`) and inequality (`<>`) operators] allows for comparing the equality of different values. +The xref:expressions/predicates/comparison-operators.adoc[equality (`=`) and inequality (`<>`) operators] allows for comparing the equality of different values. Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`).