From 5f2a64b6102edadad895e8639bc925cc8b477946 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 14:54:19 +0100 Subject: [PATCH 1/7] initial --- modules/ROOT/content-nav.adoc | 1 + modules/ROOT/images/filter_clause.svg | 1 + .../gql-conformance/supported-optional.adoc | 5 + modules/ROOT/pages/clauses/filter.adoc | 113 ++++++++++++++++++ modules/ROOT/pages/clauses/load-csv.adoc | 9 +- ...ions-additions-removals-compatibility.adoc | 11 ++ modules/ROOT/pages/syntax/keywords.adoc | 1 + 7 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 modules/ROOT/images/filter_clause.svg create mode 100644 modules/ROOT/pages/clauses/filter.adoc 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/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..ccec5c86e --- /dev/null +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -0,0 +1,113 @@ += FILTER +:description: + +[[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 + +MATCH (n) +FILTER n:Swedish +RETURN n.name AS name + +MATCH (n:Person) +FILTER n.age < 35 +RETURN n.name AS name, n.age AS age + +MATCH (p:Person)-[r:KNOWS]->(n:Person) +FILTER r.since > 2000 +RETURN p.name, n.name + +== Filter on dynamic properties + +MATCH (n:Person) +FILTER n[$propname] > 40 +RETURN n.name AS name, n.age AS age + +== `WITH`, `WHERE`, and `FILTER` + +=== FILTER as a substitute for `WITH * WHERE` +* Filter makes WITH * WHERE ... redundant + +UNWIND [1, 2, 3, 4, 5, 6] AS x +WITH x +WHERE x > 2 +RETURN x + +same as + +UNWIND [1, 2, 3, 4, 5, 6] AS x +FILTER > 2 +RETURN x + +load csv + +.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, ,G +,CCC,Michigan,info@ccc.com,G +---- + +[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}) +---- + +[source, cypher] +---- +LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row +FILTER row.Id IS NOT NULL +MERGE (c:Company {id: row.Id}) +---- + + +* Variable scope + + +* Filter cannot exist in patterns + + + + + + + + + + + + +== Differences between `FILTER` and `WHERE` + + + + diff --git a/modules/ROOT/pages/clauses/load-csv.adoc b/modules/ROOT/pages/clauses/load-csv.adoc index 084e738ff..f75b2f6ed 100644 --- a/modules/ROOT/pages/clauses/load-csv.adoc +++ b/modules/ROOT/pages/clauses/load-csv.adoc @@ -565,8 +565,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 +573,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 +581,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..ac76ddb06 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -209,6 +209,17 @@ a| label:functionality[] label:new[] +[source, cypher, role="noheader"] +---- + +---- + +| New xref:clauses/filter.adoc[`FILTER`] clause. + +a| +label:functionality[] +label:new[] + [source, cypher, role="noheader"] ---- WHEN false THEN RETURN 1 AS x 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` From f4ed81e98545cedc80d1278fc7bbdf3e559e94cc 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 11:21:41 +0100 Subject: [PATCH 2/7] second --- .../gql-conformance/analogous-cypher.adoc | 5 - modules/ROOT/pages/clauses/filter.adoc | 158 +++++++++++++++--- modules/ROOT/pages/clauses/load-csv.adoc | 3 + ...ions-additions-removals-compatibility.adoc | 18 +- .../subqueries-in-transactions.adoc | 6 +- 5 files changed, 157 insertions(+), 33 deletions(-) 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/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index ccec5c86e..ccd23db6a 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -1,5 +1,11 @@ = FILTER -:description: +: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`] subclause. +Unlike `WHERE`, `FILTER` is not a subclause, which means it can be used outside of the context of the xref:clauses/match.adoc[`MATCH`], xref:clauses/optional-match.adoc[`OPTIONAL MATCH`], and xref:clauses/with.adoc[`WITH`] clauses. +It is not, however, as versatile as `WHERE`. [[example-graph]] == Example graph @@ -26,43 +32,128 @@ CREATE (andy:Swedish:Person {name: 'Andy', age: 36}), ---- +[[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 > 2000 -RETURN p.name, n.name +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* 2 RETURN x +---- -same as - +.Filter using `FILTER` +[source, cypher] +---- UNWIND [1, 2, 3, 4, 5, 6] AS x -FILTER > 2 +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` +===== -load csv +The following two xref:clauses/load-csv.adoc[`LOAD CSV`] commands are equivalent: .companies.csv [source, csv, filename="companies.csv"] @@ -70,10 +161,11 @@ load csv Id,Name,Location,Email,BusinessType 1,Neo4j,San Mateo,contact@neo4j.com,P 2,AAA,,info@aaa.com, -3,BBB,Chicago, ,G +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 @@ -82,6 +174,7 @@ 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 @@ -89,25 +182,40 @@ FILTER row.Id IS NOT NULL MERGE (c:Company {id: row.Id}) ---- +===== -* Variable scope - - -* Filter cannot exist in patterns - +[[limitations]] +== Limitations +While `FILTER` replaces `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 ` not `WITH AS WHERE `. +Unlike `WHERE`, `FILTER` cannot be used to add filters to patterns. +.`WHERE` inside a node pattern +[source, cypher] +---- +WITH 35 AS minAge +MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(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 +---- - - - - -== Differences between `FILTER` and `WHERE` - - - - +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]. diff --git a/modules/ROOT/pages/clauses/load-csv.adoc b/modules/ROOT/pages/clauses/load-csv.adoc index f75b2f6ed..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"] ---- diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index ac76ddb06..9d215ee78 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -211,10 +211,26 @@ 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. +| New xref:clauses/filter.adoc[`FILTER`] clause used to filter queries, similar to xref:clauses/where.adoc[`WHERE`]. a| label:functionality[] 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}) From 5654def5f88ce3d9b38aa014520ae517d253b2b6 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 12:31:04 +0100 Subject: [PATCH 3/7] third --- modules/ROOT/pages/appendix/gql-conformance/index.adoc | 2 +- modules/ROOT/pages/clauses/filter.adoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index ccd23db6a..5d90d0f48 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -3,8 +3,8 @@ :table-caption!: :page-role: new-2025.03 -`FILTER` is used to add filters to queries, similar to Cypher's xref:clauses/where.adoc[`WHERE`] subclause. -Unlike `WHERE`, `FILTER` is not a subclause, which means it can be used outside of the context of the xref:clauses/match.adoc[`MATCH`], xref:clauses/optional-match.adoc[`OPTIONAL MATCH`], and xref:clauses/with.adoc[`WITH`] clauses. +`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. It is not, however, as versatile as `WHERE`. [[example-graph]] From cbbe404d8627c782727517d84393ea0df69b30a3 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 12:35:25 +0100 Subject: [PATCH 4/7] reword --- modules/ROOT/pages/clauses/filter.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ROOT/pages/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index 5d90d0f48..a7e5aa9d8 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -124,7 +124,7 @@ RETURN n.name AS name, n.age AS age |=== [[filter-with-where]] -== `FILTER` replacing `WITH * WHERE` +== `FILTER` as a substitute for `WITH * WHERE` Unlike `WHERE`, which relies on `MATCH`, `OPTIONAL MATCH`, or `WITH` to define its scope, `FILTER` -- being a clause rather than a subclause -- can filter queries independently of these clauses. This can make some queries more concise. @@ -187,11 +187,11 @@ MERGE (c:Company {id: row.Id}) [[limitations]] == Limitations -While `FILTER` replaces `WITH * WHERE ` constructs, it does not include the ability of `WITH` to manipulate the variables in scope for subsequent clauses. +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 ` not `WITH AS WHERE `. +In other words, `FILTER` only has the function of `WITH * WHERE ` and not `WITH AS WHERE `. -Unlike `WHERE`, `FILTER` cannot be used to add filters to patterns. +Additionally, unlike `WHERE`, `FILTER` cannot be used to add filters to patterns. .`WHERE` inside a node pattern [source, cypher] From e70abd867459864d2ea8e01ac99e1ff57719a65c 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 15:26:38 +0100 Subject: [PATCH 5/7] post review edit --- modules/ROOT/pages/clauses/filter.adoc | 142 ++++++++++++++++++------- 1 file changed, 105 insertions(+), 37 deletions(-) diff --git a/modules/ROOT/pages/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index a7e5aa9d8..40b682aee 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -4,8 +4,7 @@ :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. -It is not, however, as versatile as `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 @@ -123,10 +122,111 @@ RETURN n.name AS name, n.age AS age 2+d|Rows: 1 |=== +[[filter-where-differeces]] +== Differences between `FILTER` and `WHERE` + +`FILTER` and `WHERE` are both used to apply filter to queries. +However, there are a number of important differences between them that arise from the fact that `FILTER` is a clause and `WHERE` is a subclause: + +* While `WHERE` should not be understood as a filter after the matching is finished (it should rather be seen as adding constraints to a described pattern), `FILTER` should be understood as performing post-match filtering, and not as adding constraints to a described patterns. +* `FILTER` cannot be used within `MATCH`, `OPTIONAL MATCH`, or `WITH` clauses but only alongside them. +This means that, unlike `WHERE`, `FILTER` cannot be used to add filters inside patterns. + +.Distinction between `FILTER` and `WHERE` +===== + +This `OPTIONAL MATCH` example highlights the differences between the `WHERE` subclause and the `FILTER` clause. + +.`WHERE` constraining an `OPTIONAL MATCH` pattern +[source, cypher] +---- +UNWIND [32,37,40] AS ages +OPTIONAL MATCH (p:Person) +WHERE p.age = ages +RETURN p.name AS name, p.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` +=== `FILTER` as a substitute for `WITH * WHERE` -Unlike `WHERE`, which relies on `MATCH`, `OPTIONAL MATCH`, or `WITH` to define its scope, `FILTER` -- being a clause rather than a subclause -- can filter queries independently of these clauses. +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: @@ -184,38 +284,6 @@ MERGE (c:Company {id: row.Id}) ===== -[[limitations]] -== Limitations - -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. +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 `. - -Additionally, unlike `WHERE`, `FILTER` cannot be used to add filters to patterns. - -.`WHERE` inside a node pattern -[source, cypher] ----- -WITH 35 AS minAge -MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(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]. From c224c1a756258a1e01058e5eadc5a64c75ee9a92 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 15:34:39 +0100 Subject: [PATCH 6/7] rephrase --- modules/ROOT/pages/clauses/filter.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index 40b682aee..9ae16b2bc 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -162,7 +162,7 @@ Because `WHERE` is a subclause belonging to `OPTIONAL MATCH`, it only filters th In this case, `OPTIONAL MATCH` always keeps all rows from xref:clauses/unwind.adoc[`UNWIND`], and `WHERE` does not remove any rows returning `NULL.` The same is not true if `WHERE` is exchanged for `FILTER`: -.`FILTER` adding a post-filtering to `OPTIONAL MATCH` +.`FILTER` adding post-filtering to `OPTIONAL MATCH` [source, cypher] ---- UNWIND [32,37,40] AS ages @@ -187,7 +187,7 @@ That is, when `OPTIONAL MATCH` fails to find a match and `p` is `NULL`, `FILTER ===== -.`FILTER` cannot be used to add filter to patterns +.`FILTER` cannot be used within patterns ===== Because `WHERE` is a subclause qualifying a described pattern, it can be used inside patterns. From 496c21a01f8bbb9714cc10706e79d520e7e45d33 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 17:25:57 +0100 Subject: [PATCH 7/7] Update modules/ROOT/pages/clauses/filter.adoc Co-authored-by: Hannes Voigt <30618026+hvub@users.noreply.github.com> --- modules/ROOT/pages/clauses/filter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/clauses/filter.adoc b/modules/ROOT/pages/clauses/filter.adoc index 9ae16b2bc..cbe006eba 100644 --- a/modules/ROOT/pages/clauses/filter.adoc +++ b/modules/ROOT/pages/clauses/filter.adoc @@ -182,7 +182,7 @@ RETURN p.name 2+d|Rows: 2 |=== -Unlike `WHERE`, FILTER is not part of the `OPTIONAL MATCH` and so removes entire rows from the result set based on the condition provided within the expression. +Unlike `WHERE`, `FILTER` is not part of the `OPTIONAL MATCH` and so removes entire rows from the result set based on the condition provided within the expression. That is, when `OPTIONAL MATCH` fails to find a match and `p` is `NULL`, `FILTER p.age = ages` cannot be evaluated, causing the entire row to be removed. =====