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 @@
+
\ 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.
=====