WHERE#
GQL clause
Constrains a pattern or filters a projection: rows for which the condition is not true are dropped.
Synopsis#
bold keywords · italic placeholders you supply · [ ] optional · ... repeatable. See Syntax conventions.
Description#
WHERE is not a clause of its own. It attaches to one of three clauses and takes
its meaning from the one it attaches to:
On
MATCHit is part of the match. Every variable in the pattern is bound when the condition runs, and a match that fails it is not produced.On
OPTIONAL MATCHit is part of the optional match. A row that fails the condition is kept, with the pattern’s variables null.On
WITHit filters the projected rows, after any aggregation. This is where a condition over an aggregate goes.
The condition must be a boolean expression. A value of another type is not
coerced: a jsonb string or number in a condition raises an error rather than
being treated as true. Aggregates are not allowed in a WHERE. Put them in a
WITH and filter the result.
WHERE on its own is a syntax error, and a query cannot end with it. The
standalone, chainable filter is FILTER, which takes both
FILTER condition and FILTER WHERE condition.
Parameters#
- condition
An expression of type boolean. Combine tests with
AND,OR,XORandNOT.
Examples#
Every example runs against the example dataset.
Compare a property#
MATCH (m:movie) WHERE m.year > 2015
RETURN m.title AS film, m.year AS year
ORDER BY year, film;
film | year
----------------------------+------
"Blade Runner 2049" | 2017
"The Matrix Resurrections" | 2021
(2 rows)
Combine conditions#
MATCH (m:movie) WHERE m.genre = 'Action' AND m.year < 1995
RETURN m.title AS film
ORDER BY film;
film
------------------------------
"Terminator 2: Judgment Day"
"The Terminator"
(2 rows)
XOR is available alongside AND, OR and NOT:
MATCH (m:movie) WHERE m.genre = 'Action' XOR m.year < 1995
RETURN count(*) AS n;
n
---
8
(1 row)
Test a label#
label() returns an element’s label as a string, for vertices and edges alike:
MATCH (n) WHERE label(n) = 'person'
RETURN count(*) AS n;
n
----
28
(1 row)
IN over a list of names is how a pattern matches several edge labels, which an
edge pattern cannot express:
MATCH (p:person)-[r]->(m:movie)
WHERE label(r) IN ['acted_in', 'directed']
RETURN count(r) AS edges;
edges
-------
46
(1 row)
Match on a substring or a pattern#
STARTS WITH, ENDS WITH and CONTAINS test a string property:
MATCH (m:movie) WHERE m.title STARTS WITH 'The Matrix'
RETURN count(*) AS n;
n
---
4
(1 row)
MATCH (m:movie) WHERE m.title ENDS WITH 'Runner'
RETURN m.title AS film;
film
----------------
"Blade Runner"
(1 row)
=~ matches a POSIX regular expression:
MATCH (m:movie) WHERE m.title =~ '^Blade.*'
RETURN m.title AS film
ORDER BY film;
film
---------------------
"Blade Runner"
"Blade Runner 2049"
(2 rows)
Negate with NOT; there is no !~ for a property and no NOT LIKE:
MATCH (m:movie) WHERE NOT m.title STARTS WITH 'The'
RETURN count(*) AS n;
n
----
11
(1 row)
Test for a missing property#
A property that was never set reads as null, so IS NULL is the test for its
absence:
MATCH (m:movie) WHERE m.runtime IS NULL
RETURN count(*) AS n;
n
----
18
(1 row)
Test for a pattern#
A subquery expression tests whether a pattern exists without binding anything from it:
MATCH (p:person) WHERE EXISTS { MATCH (p)-[:directed]->() }
RETURN p.name AS director
ORDER BY director
LIMIT 4;
director
---------------------
"Andrew Niccol"
"Christopher Nolan"
"Denis Villeneuve"
"James Cameron"
(4 rows)
Filter an aggregate with WITH#
An aggregate in a WHERE is refused:
MATCH (p:person)-[:acted_in]->(m:movie) WHERE count(m) > 1
RETURN p.name;
ERROR: aggregate functions are not allowed in WHERE
Aggregate in a WITH, then filter the result. The WITH clause’s own WHERE
runs after the grouping:
MATCH (p:person)-[:acted_in]->(m:movie)
WITH p, count(m) AS films WHERE films >= 3
RETURN p.name AS actor, films
ORDER BY actor;
actor | films
-------------------------+-------
"Arnold Schwarzenegger" | 3
"Keanu Reeves" | 4
(2 rows)
Notes#
A property holds a jsonb value, and comparing it to a literal compares jsonb values. In jsonb ordering a number sorts above every string, so a quoted number compared against a numeric property is not the comparison you meant:
MATCH (m:movie {title: 'The Matrix'})
RETURN m.year > '9999' AS num_gt_str, m.year > 1990 AS num_gt_num;
num_gt_str | num_gt_num
------------+------------
t | t
(1 row)
Write the literal in the property’s own type, or cast the property with
m.year::int > 1990, when the stored type is not the one you expect.
Note
WHERE takes a boolean and nothing else. WHERE m.title raises
cannot cast "The Matrix" (jsonb type string) to boolean, where a language with
truthiness would treat a non-empty string as true. Compare explicitly, or use
IS NOT NULL.
Compatibility#
Labeled predicate (ISO/IEC 39075 §19.9)#
The standard writes a label test as n IS LABELED movie, with n:movie as a
shorthand. AgensGraph accepts neither; use label(n) = 'movie'.
Pattern predicates#
A bare pattern is not an expression, so it cannot be used as a predicate:
WHERE (m)-[:related_to]->(:movie) is a syntax error, and so is the unlabelled
WHERE (m)-[]->(). Wrap it in EXISTS { MATCH ... }, which the standard defines
in §19.4 and AgensGraph supports.
SQL string operators#
LIKE and NOT LIKE are not part of a Cypher condition.
WHERE m.title LIKE 'Blade%' is a syntax error, with or without a cast. Use
STARTS WITH, ENDS WITH, CONTAINS or =~. To use the SQL operators, wrap the
Cypher in SQL; see
combining SQL and Cypher.
See also#
MATCH— the clauseWHEREmost often attaches toOPTIONAL MATCH— where a failed condition keeps the rowWITH— filtering after a projection or an aggregateFILTER— the standalone filter clauseSubquery expressions —
EXISTS,COUNT,COLLECTand the rest