diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 5bc92b6e8..b1bf4a547 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -76,6 +76,7 @@ ** xref:expressions/mathematical-operators.adoc[] ** xref:expressions/string-operators.adoc[] ** xref:expressions/temporal-operators.adoc[] +** xref:expressions/list-expressions.adoc[] ** xref:expressions/conditional-expressions.adoc[] * xref:functions/index.adoc[] diff --git a/modules/ROOT/images/list_expressions_graph.svg b/modules/ROOT/images/list_expressions_graph.svg new file mode 100644 index 000000000..3fdbc2779 --- /dev/null +++ b/modules/ROOT/images/list_expressions_graph.svg @@ -0,0 +1 @@ +WORKS_FORWORKS_FORWORKS_FORWORKS_FORWORKS_FORPersonname:'Alice'age:65role:'Project manager'skills:['Java', 'Python']Personname:'Cecil'age:25role:'Software developer'skills:['Java', 'Python']Personname:'Cecilia'age:31role:'Software developer'skills:['JavaScript', 'TypeScript']Personname:'Charlie'age:61role:'Security engineer'skills:['C++', 'Python']Personname:'Daniel'age:39role:'Director'skills:['Ruby', 'Go']Personname:'Eskil'age:39role:'CEO'skills:['Java', 'C++', 'Python'] \ No newline at end of file diff --git a/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc b/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc index ae7fe27a0..1dabfe915 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/additional-cypher.adoc @@ -623,7 +623,7 @@ GQL supports `GRAPH TYPES` as a way of constraining a graph schema, but does not | xref:syntax/operators.adoc#query-operator-comparison-string-specific[`STARTS WITH`, `CONTAINS`, `ENDS WITH`, and regular expressions]. | `STRING` comparison operators. -| xref:syntax/operators.adoc#query-operators-list[`IN`] +| xref:expressions/predicates/list-operators.adoc[`IN`] | `IN` predicate for `LIST` values. |=== diff --git a/modules/ROOT/pages/expressions/index.adoc b/modules/ROOT/pages/expressions/index.adoc index 3dbcd364e..f2b0fc0ec 100644 --- a/modules/ROOT/pages/expressions/index.adoc +++ b/modules/ROOT/pages/expressions/index.adoc @@ -14,5 +14,6 @@ For details and examples of specific expressions, see the following sections: * xref:expressions/mathematical-operators.adoc[]: `+`, `-`, `*`, `/`, `%`, `^`. * xref:expressions/string-operators.adoc[]: `+`, `||` * xref:expressions/temporal-operators.adoc[]: `+`, `-`, `*`, `/` +* xref:expressions/list-expressions.adoc[]: information about list concatenation operators (`||`, `+`), list element access, list slicing, and list as well as pattern comprehensions. * xref:expressions/conditional-expressions.adoc[] diff --git a/modules/ROOT/pages/expressions/list-expressions.adoc b/modules/ROOT/pages/expressions/list-expressions.adoc new file mode 100644 index 000000000..494fa1c9c --- /dev/null +++ b/modules/ROOT/pages/expressions/list-expressions.adoc @@ -0,0 +1,644 @@ += List expressions +:description: Information about Cypher's list expressions. +:table-caption!: + +List expressions allow you to manipulate and query xref:values-and-types/lists.adoc[`LIST`] values in Cypher. + +For more expressions that evaluate to a `LIST` value, see xref:functions/list.adoc[List functions]. + +For information about how to check membership in a `LIST` using the `IN` operator, see xref:expressions/predicates/list-operators.adoc[Predicates -> List operators]. + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::list_expressions_graph.svg[width="700",role="middle"] + +To recreate the graph, run the following query against an empty Neo4j database: + +[source, cypher] +---- +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', skills: ['Java', 'Python']}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', skills: ['Java', 'Python']}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer', skills: ['JavaScript', 'TypeScript']}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer', skills: ['C++', 'Python']}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', skills: ['Ruby', 'Go']}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', skills: ['Java', 'C++', 'Python']}), + + (cecil)-[:WORKS_FOR]->(alice), + (cecilia)-[:WORKS_FOR]->(alice), + (charlie)-[:WORKS_FOR]->(daniel), + (alice)-[:WORKS_FOR]->(daniel), + (daniel)-[:WORKS_FOR]->(eskil) +---- + + +[[list-element-access]] +== List element access + +The subscript operator, `[]`, can be used to access specific elements in a `LIST`. +`[0]` refers to the first element in a `LIST`, `[1]` to the second, and so on. +`[-1]` refers to the last element in a `LIST`, `[-2]` to the penultimate element, and so on. + +.Access individual elements in a `LIST` +[source, cypher] +---- +WITH [1, 2, 3, 4] AS list +RETURN list[0] AS firstElement + list[2] AS thirdElement + list[-1] AS finalElement +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 2 | n] AS filteredList +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(alice) | employee.name] AS employees +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(alice) WHERE employee.age > 30 | employee.name || ', ' || toString(employee.age)] AS employeesAbove30 +---- + +.Result +[role="queryresult",options="header,footer",cols="1*+(superior:Person) | superior.skills] AS superiorsSkills +---- + +Pattern comprehension only supports only the xref:patterns/reference.adoc#variable-length-relationships[variable-length relationships] syntax. +The below query uses a pattern comprehension to collect the skills of all superiors in the chain above `Cecil`. +The xref:functions/list.adoc#functions-reduce[`reduce()`] function concatenates these skills into a single `LIST`, and xref:clauses/unwind.adoc[`UNWIND`] is used to flatten this `LIST` before returning the distinct skills in a new `LIST`. + +.Allowed: variable-length pattern comprehension using variable-length relationship syntax +[source, cypher] +---- +MATCH (cecil:Person {name: 'Cecil'}) +WITH [(cecil)-[:WORKS_FOR*]->(superior:Person) | superior.skills] AS allSuperiorsSkills +WITH reduce(accumulatedSkills = [], superiorSkills IN allSuperiorsSkills | accumulatedSkills || superiorSkills) AS allSkills +UNWIND allSkills AS superiorsSkills +RETURN collect(DISTINCT superiorsSkills) AS distinctSuperiorsSkills +---- + +.Result +[role="queryresult",options="header,footer",cols="1* Lists] +* xref:expressions/predicates/list-operators.adoc[Predicates -> List operators] +* xref:expressions/list-expressions.adoc[List expressions] [[example-graph]] == Example graph diff --git a/modules/ROOT/pages/syntax/operators.adoc b/modules/ROOT/pages/syntax/operators.adoc index 0766800d8..3c4195f8e 100644 --- a/modules/ROOT/pages/syntax/operators.adoc +++ b/modules/ROOT/pages/syntax/operators.adoc @@ -12,7 +12,6 @@ This page contains an overview of the available Cypher operators. | xref::syntax/operators.adoc#query-operators-aggregation[Aggregation operators] | `DISTINCT` | xref::syntax/operators.adoc#query-operators-property[Property operators] | `.` for static property access, `[]` for dynamic property access, `=` for replacing all properties, `+=` for mutating specific properties | xref::syntax/operators.adoc#query-operators-map[Map operators] | `.` for static value access by key, `[]` for dynamic value access by key -| xref::syntax/operators.adoc#query-operators-list[List operators] | `+` and `\|\|` (list concatenation), `IN` to check existence of an element in a list, `[]` for accessing element(s) dynamically |=== @@ -251,213 +250,3 @@ RETURN a[$myKey] AS result |=== More information can be found in the xref::values-and-types/maps.adoc[Maps chapter]. - -[[query-operators-list]] -== List operators - -The list operators comprise: - -* concatenating lists `l~1~` and `l~2~`: `[l~1~] + [l~2~]` and `[l~1~] || [l~2~]` -* checking if an element `e` exists in a list `l`: `e IN [l]` -* dynamically accessing an element(s) in a list using the subscript operator: `[]` - -[NOTE] -==== -The behavior of the `IN` and `[]` operators with respect to `null` is detailed xref::values-and-types/working-with-null.adoc[here]. -==== - - -[[syntax-concatenating-two-lists]] -=== Concatenating two lists using `+` - -.Query -[source, cypher] ----- -RETURN [1,2,3,4,5] + [6,7] AS myList ----- - -.Result -[role="queryresult",options="header,footer",cols="1* 0 -RETURN count(n) ----- - -As long as `labels(n)` returns either `Person` or `Employee` (or both), the query will return a value greater than zero. - - -[[syntax-accessing-elements-in-a-list]] -=== Accessing elements in a list using the `[]` operator - -.Query -[source, cypher] ----- -WITH ['Anne', 'John', 'Bill', 'Diane', 'Eve'] AS names -RETURN names[1..3] AS result ----- - -The square brackets will extract the elements from the start index `1`, and up to (but excluding) the end index `3`. - -.Result -[role="queryresult",options="header,footer",cols="1* Predicates -> List operators]. +For information list concatenation (`+` and `||`), list element access and slicing (`[]`), as well as list and pattern comprehensions, see xref:expressions/list-expressions.adoc[List expressions]. [[cypher-lists-general]] == Lists in general @@ -70,7 +67,7 @@ RETURN list[2] 1+d|Rows: 1 |=== -=== List range and size +== List range and size The below examples use the xref::functions/list.adoc#functions-range[`range`] function to create lists. This function returns a list containing all numbers between given start and end numbers. @@ -212,169 +209,40 @@ RETURN size(range(0, 10)[0..3]) AS list |=== -[[cypher-pattern-comprehension]] -== Pattern comprehension - -Pattern comprehension is a syntactic construct available in Cypher for creating a list based on matchings of a pattern. -A pattern comprehension matches the specified pattern like a normal `MATCH` clause, with predicates like a normal `WHERE` clause, but yields a custom projection as specified. - -=== Example graph - -The following graph is used for examples below: - -image::values_and_types_lists_graph.svg[] - -To recreate the graph, run the following query against an empty Neo4j database: - -[source, cypher, role=test-setup] ----- -CREATE - (keanu:Person {name: 'Keanu Reeves'}), - (johnnyMnemonic:Movie {title: 'Johnny Mnemonic', released: 1995}), - (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003}), - (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003}), - (theReplacements:Movie {title: 'The Replacements', released: 2000}), - (theMatrix:Movie {title: 'The Matrix', released: 1999}), - (theDevilsAdvocate:Movie {title: 'The Devils Advocate', released: 1997}), - (theMatrixResurrections:Movie {title: 'The Matrix Resurrections', released: 2021}), - (keanu)-[:ACTED_IN]->(johnnyMnemonic), - (keanu)-[:ACTED_IN]->(theMatrixRevolutions), - (keanu)-[:ACTED_IN]->(theMatrixReloaded), - (keanu)-[:ACTED_IN]->(theReplacements), - (keanu)-[:ACTED_IN]->(theMatrix), - (keanu)-[:ACTED_IN]->(theDevilsAdvocate), - (keanu)-[:ACTED_IN]->(theMatrixResurrections) ----- - -=== Examples +== Storing lists as properties -This example returns a list that contains the year when the movies were released. -The pattern matching in the pattern comprehension looks for `Matrix` in the movie title and that the node `keanu` (`Person` node with the name `Keanu Reeves`) has a relationship with the movie. +It is possible to store homogenous lists of simple values as properties. -.Query +.Allowed - store homogenous list as a property [source, cypher] ---- -MATCH (keanu:Person {name: 'Keanu Reeves'}) -RETURN [(keanu)-->(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years +CREATE (n:Label) +SET n.listProperty = [1, 2, 3] +RETURN n.listProperty AS homogenousListProperty ---- .Result [role="queryresult",options="header,footer",cols="1*(b:Movie) | b.title] AS movieTitles -SET keanu.resume = movieTitles -RETURN keanu.resume ----- - -.Result -[role="queryresult",options="header,footer",cols="1*(b:Movie) | b.title] + [(keanu)-->(b:Movie) | b.released] AS movieTitles -SET keanu.resume = movieTitles -RETURN keanu.resume +CREATE (n:Label) +SET n.listProperty = [1, "hello", .45, date()] +RETURN n.listProperty AS heterogenousListProperty ---- +.Error [source,error] ---- Neo4j only supports a subset of Cypher types for storage as singleton or array properties. Please refer to section cypher/syntax/values of the manual for more details. ---- - - -[[cypher-list-comprehension]] -== List comprehension - -List comprehension is a syntactic construct available in Cypher for creating a list based on existing lists. - -For example, the following query returns a new list from the previously created `resume` property (a list of strings) of `Keanu Reeves`: - -.Query -[source, cypher] ----- -MATCH (keanu:Person {name:'Keanu Reeves'}) -RETURN [x IN keanu.resume WHERE x contains 'The Matrix'] AS matrixList ----- - -.Result -[role="queryresult",options="header,footer",cols="1*