diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 2d08c7ecc..0e4e2ba8b 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -15,6 +15,7 @@ ** xref:clauses/call.adoc[] ** xref:clauses/create.adoc[] ** xref:clauses/delete.adoc[] +** xref:clauses/filter.adoc[] ** xref:clauses/finish.adoc[] ** xref:clauses/foreach.adoc[] ** xref:clauses/limit.adoc[] diff --git a/modules/ROOT/images/filter_clause.svg b/modules/ROOT/images/filter_clause.svg new file mode 100644 index 000000000..f15e25ef6 --- /dev/null +++ b/modules/ROOT/images/filter_clause.svg @@ -0,0 +1 @@ +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/appendix/gql-conformance/analogous-cypher.adoc b/modules/ROOT/pages/appendix/gql-conformance/analogous-cypher.adoc index 0a13525c5..9c28c5f97 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/analogous-cypher.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/analogous-cypher.adoc @@ -31,11 +31,6 @@ These codes order the features in the table below. | * GQL's `PERCENTILE_CONT()` function is equivalent to Cypher's xref:functions/aggregating.adoc#functions-percentilecont[`percentileCont()`] function. * GQL's `PERCENTILE_DISC()` function is equivalent to Cypher's xref:functions/aggregating.adoc#functions-percentiledisc[`percentileDisc()`] function. -| GQ08 -| `FILTER` statement -| Selects a subset of the records of the current working table. -Cypher uses xref:clauses/with.adoc[`WITH`] instead. - | GQ09 | `LET` statement | Adds columns to the current working table. diff --git a/modules/ROOT/pages/appendix/gql-conformance/index.adoc b/modules/ROOT/pages/appendix/gql-conformance/index.adoc index 2a09dc445..3b2ab5ad8 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/index.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/index.adoc @@ -1,7 +1,7 @@ :description: Overview of Cypher's conformance to GQL. = GQL conformance -*Last updated*: 21 February 2025 + +*Last updated*: 27 February 2025 + *Neo4j version*: 2025.03 GQL is the new link:https://www.iso.org/home.html[ISO] International Standard query language for graph databases. diff --git a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc index 64d09b71c..3cf0f8806 100644 --- a/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc +++ b/modules/ROOT/pages/appendix/gql-conformance/supported-optional.adoc @@ -173,6 +173,11 @@ For example, GQL’s graph reference values `CURRENT_GRAPH` and `CURRENT_PROPERT | xref:queries/composed-queries/combined-queries.adoc[`UNION`] | +| GQ08 +| `FILTER` statement +| xref:clauses/filter.adoc[`FILTER`] +| + | GQ13 | `ORDER BY` and page statement: `LIMIT` | xref:clauses/limit.adoc[`LIMIT`], xref:clauses/order-by.adoc[`ORDER BY`] diff --git a/modules/ROOT/pages/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc new file mode 100644 index 000000000..cbe006eba --- /dev/null +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -0,0 +1,289 @@ += FILTER +:description: Information about Cypher's `FILTER` clause. +:table-caption!: +:page-role: new-2025.03 + +`FILTER` is used to add filters to queries, similar to Cypher's xref:clauses/where.adoc[`WHERE`]. +Unlike `WHERE`, `FILTER` is not a subclause, which means it can be used independently of the xref:clauses/match.adoc[`MATCH`], xref:clauses/optional-match.adoc[`OPTIONAL MATCH`], and xref:clauses/with.adoc[`WITH`] clauses, but not within them. + +[[example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::filter_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: 38}), + (peter:Person {name: 'Peter', age: 35}), + (lisa:Person {name: 'Lisa', age: 48}), + (john:Person {name: 'John', age: 40}), + (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: 2021}]->(susan) +---- + + +[[basic-filtering]] +== Basic filtering + +.Filter on a node label +[source, cypher] +---- +MATCH (n) +FILTER n:Swedish +RETURN n.name AS name +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(n:Person) +FILTER r.since > 2010 +RETURN p.name AS person, + r.since AS knowsSince, + n.name AS otherPerson +---- + +.Result +[role="queryresult",options="header,footer",cols="3* 40 +RETURN n.name AS name, n.age AS age +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(b:Person WHERE b.age > minAge) +RETURN b.name AS name` +---- +.Result +[role="queryresult",options="header,footer",cols="1*(b:Person FILTER b.age > minAge) +RETURN b.name AS name +---- + +For more information about how to use `WHERE` in fixed-length and variable-length pattern matching, see xref:clauses/where.adoc#filter-patterns[`WHERE` -> Filter patterns]. + +===== + +[[filter-with-where]] +=== `FILTER` as a substitute for `WITH * WHERE` + +Unlike `WHERE`, which relies on `MATCH`, `OPTIONAL MATCH`, or `WITH` to define its scope, `FILTER` can filter queries independently of these clauses. +This can make some queries more concise. + +For example, the following two queries are equivalent: + +.Filter using `WITH * WHERE` +[source, cypher] +---- +UNWIND [1, 2, 3, 4, 5, 6] AS x +WITH x +WHERE x > 2 +RETURN x +---- + +.Filter using `FILTER` +[source, cypher] +---- +UNWIND [1, 2, 3, 4, 5, 6] AS x +FILTER x > 2 +RETURN x +---- + +As such, `FILTER` can be seen as a substitute for the `WITH * WHERE ` constructs in Cypher. + +.Using `FILTER` instead of `WITH * WHERE` in `LOAD CSV` +===== + +The following two xref:clauses/load-csv.adoc[`LOAD CSV`] commands are equivalent: + +.companies.csv +[source, csv, filename="companies.csv"] +---- +Id,Name,Location,Email,BusinessType +1,Neo4j,San Mateo,contact@neo4j.com,P +2,AAA,,info@aaa.com, +3,BBB,Chicago, info@ ,G +,CCC,Michigan,info@ccc.com,G +---- + +.`LOAD CSV` using `WITH * WHERE` +[source, cypher] +---- +LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row +WITH row +WHERE row.Id IS NOT NULL +MERGE (c:Company {id: row.Id}) +---- + +.`LOAD CSV` using `FILTER` +[source, cypher] +---- +LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row +FILTER row.Id IS NOT NULL +MERGE (c:Company {id: row.Id}) +---- + +===== + +However, while `FILTER` can act as a substitute for `WITH * WHERE ` constructs, it does not include the ability of `WITH` to manipulate the variables in scope for subsequent clauses. +Nor can `FILTER` alias or create new variables. +In other words, `FILTER` only has the function of `WITH * WHERE ` and not `WITH AS WHERE `. diff --git a/modules/ROOT/pages/clauses/load-csv.adoc b/modules/ROOT/pages/clauses/load-csv.adoc index 084e738ff..5036d7a45 100644 --- a/modules/ROOT/pages/clauses/load-csv.adoc +++ b/modules/ROOT/pages/clauses/load-csv.adoc @@ -551,6 +551,9 @@ Neo4j does not store `null` values. In the file `companies.csv`, some rows do not specify values for some columns. The examples show several options of how to handle `null` values. +[NOTE] +The queries in this example use xref:clauses/filter.adoc#filter-with-where[`FILTER`] (introduced in Neo4j 2025.03) as a replacement for `WITH * WHERE `. + .companies.csv [source, csv, filename="companies.csv"] ---- @@ -565,8 +568,7 @@ Id,Name,Location,Email,BusinessType [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row -WITH row -WHERE row.Id IS NOT NULL +FILTER row.Id IS NOT NULL MERGE (c:Company {id: row.Id}) ---- @@ -574,8 +576,7 @@ MERGE (c:Company {id: row.Id}) [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row -WITH row -WHERE row.Id IS NOT NULL +FILTER row.Id IS NOT NULL MERGE (c:Company {id: row.Id, hqLocation: coalesce(row.Location, "Unknown")}) ---- @@ -583,8 +584,7 @@ MERGE (c:Company {id: row.Id, hqLocation: coalesce(row.Location, "Unknown")}) [source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row -WITH row -WHERE row.Id IS NOT NULL +FILTER row.Id IS NOT NULL MERGE (c:Company {id: row.Id}) SET c.email = nullIf(trim(row.Email), "") ---- diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index d23d39a03..9d215ee78 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -209,6 +209,33 @@ a| label:functionality[] label:new[] +[source, cypher, role="noheader"] +---- +UNWIND [1, 2, 3, 4, 5, 6] AS x +FILTER x > 2 +RETURN x +---- + +[source, cypher, role="noheader"] +---- +UNWIND [1, 2, 3, 4, 5, 6] AS x +FILTER x > 2 +RETURN x +---- + +[source, cypher, role="noheader"] +---- +LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row +FILTER row.Id IS NOT NULL +MERGE (c:Company {id: row.Id}) +---- + +| New xref:clauses/filter.adoc[`FILTER`] clause used to filter queries, similar to xref:clauses/where.adoc[`WHERE`]. + +a| +label:functionality[] +label:new[] + [source, cypher, role="noheader"] ---- WHEN false THEN RETURN 1 AS x diff --git a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc index c6f0e495d..3390e4667 100644 --- a/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc +++ b/modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc @@ -748,6 +748,9 @@ RETURN status.transactionId AS transaction, status.committed AS commitStatus, st ===== While failed transactions may be more efficiently retried using a link:{neo4j-docs-base-uri}/create-applications[driver], below is an example how failed transactions can be retried within the same Cypher query: +[NOTE] +The query below uses xref:clauses/filter.adoc#filter-with-where[`FILTER`] (introduced in Neo4j 2025.03) as a replacement for `WITH * WHERE `. + .Query retrying failed transactions [source, cypher] ---- @@ -757,8 +760,7 @@ CALL (row) { MERGE (y:Year {year: row.year}) MERGE (m)-[r:RELEASED_IN]->(y) } IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status -WITH * -WHERE status.committed = false +FILTER status.committed = false CALL (row) { MERGE (m:Movie {movieId: row.movieId}) MERGE (y:Year {year: row.year}) diff --git a/modules/ROOT/pages/syntax/keywords.adoc b/modules/ROOT/pages/syntax/keywords.adoc index 4e7a24477..dc71b20a2 100644 --- a/modules/ROOT/pages/syntax/keywords.adoc +++ b/modules/ROOT/pages/syntax/keywords.adoc @@ -164,6 +164,7 @@ Note that with future functionality, Cypher may be extended with additional keyw * `FALSE` * `FIELDTERMINATOR` * `FINISH` +* `FILTER` * `FLOAT` * `FOR` * `FOREACH`