Reading clauses
MATCH, OPTIONAL MATCH, WHERE, RETURN, ORDER BY, SKIP, LIMIT, WITH and UNWIND in relate, with their grammar, null and ordering rules, and errors.
cypher
Reading clauses find rows and shape them. MATCH and OPTIONAL MATCH bind variables from patterns, WHERE filters, WITH and RETURN project, ORDER BY, SKIP and LIMIT sort and page, and UNWIND turns a list into rows. relate runs them with Cypher 25 semantics by default, and also accepts the GQL The ISO standard graph query language; Neo4j's Cypher 25 aligns Cypher with it, and relate follows that alignment. Read more in the glossary spellings FOR, LET and FILTER. A statement One unit of Cypher that relate parses, plans and runs, such as a MATCH … RETURN read or a CREATE write; a script holds several, separated by ;. Read more in the glossary that only reads is a query A statement that reads, where the contrast with writing matters; also relate query, the command that runs a statement session. Read more in the glossary; the pattern syntax inside MATCH is on the patterns page.
Synopsis
[OPTIONAL] MATCH <pattern> [, <pattern> …] [WHERE <predicate>]
WITH [DISTINCT] <expression> AS <alias> [, …] | *
[GROUP BY <key>, … | GROUP BY () | GROUP BY ALL]
[ORDER BY <expression> [ASC | ASCENDING | DESC | DESCENDING] [, …]]
[SKIP <n> | OFFSET <n>] [LIMIT <n>]
[WHERE <predicate>]
UNWIND <list> AS <variable>
FOR <variable> IN <list>
LET <variable> = <expression> [, …]
FILTER [WHERE] <predicate>
RETURN [DISTINCT | ALL] <expression> [AS <alias>] [, …] | *
[GROUP BY <key>, … | GROUP BY () | GROUP BY ALL]
[ORDER BY <expression> [ASC | ASCENDING | DESC | DESCENDING] [, …]]
[SKIP <n> | OFFSET <n>] [LIMIT <n>]
ORDER BY <expression> [ASC | ASCENDING | DESC | DESCENDING] [, …]
SKIP <n> | OFFSET <n>
LIMIT <n>Clauses
| Clause | Takes | Produces | Notes |
|---|---|---|---|
MATCH | One or more patterns | One row per way the patterns match; no match, no rows | Comma-separated patterns form a cartesian product, joined on shared variables. Within one MATCH, each relationship is matched at most once per row |
OPTIONAL MATCH | One or more patterns | The input row with the new variables bound, or bound to null when nothing matches | Its WHERE filters the optional part only; a row whose optional part fails the filter is kept with nulls |
WHERE | A predicate | The rows where it is true | A row where the predicate is null is dropped, the same as false. Follows MATCH, OPTIONAL MATCH or WITH |
WITH | Projection items | A new row per input row, holding only the projected variables | Every expression needs AS; a bare variable does not. Variables not projected go out of scope |
RETURN | Projection items | The result columns | A column without AS is named after its expression text, such as p.name. Two columns with one name fail |
ORDER BY | Sort keys | The rows sorted | ASC is the default. After DISTINCT or an aggregation, a sort key can use the projected values, the projected expressions, and aggregates over the group; other row variables are out of scope. Also a clause of its own between other clauses, as in GQL |
SKIP, OFFSET | A non-negative integer | All but the first n rows | OFFSET is the GQL spelling of SKIP. Also a clause of its own between other clauses, as in GQL |
LIMIT | A non-negative integer | At most n rows | LIMIT 0 returns no rows. Also a clause of its own between other clauses, as in GQL |
UNWIND | A list and a variable name | One row per element | [] and null give no rows; a value that is not a list gives one row |
FOR | A variable name and a list | The same rows as UNWIND | GQL spelling; the variable comes first. FOR <list> AS <variable> is a parse error. The CLI accepts it; the WebAssembly build the cells on this page use does not (example) |
LET | <variable> = <expression> pairs | The input row with new variables | A pair cannot refer to another pair of the same LET; a second LET can refer to the first |
FILTER | A predicate | The rows where it is true | A clause of its own; WHERE after it is optional |
Projection
| Form | Result |
|---|---|
RETURN *, WITH * | Every variable in scope, as one column each |
RETURN DISTINCT, WITH DISTINCT | Duplicate rows removed |
RETURN ALL | Every row kept; the default, written out |
| Aggregate beside plain items | Plain items are the grouping keys: RETURN p.role AS role, count(*) AS people counts per role |
GROUP BY <key>, … | The listed aliases are the grouping keys, in place of the inferred ones |
GROUP BY () | One group: aggregates over every row |
GROUP BY ALL | Every plain item is a key, as without GROUP BY |
WHERE predicates
| Predicate | Form | true when |
|---|---|---|
| Comparison | =, <>, <, >, <=, >= | The comparison holds; a comparison with null is null |
| Boolean | AND, OR, XOR, NOT | Three-valued: null AND false is false, null OR true is true, otherwise null propagates |
| String | STARTS WITH, ENDS WITH, CONTAINS | Case-sensitive match on strings |
| Regular expression | <string> =~ '<regex>' | The whole string matches |
| Null test | IS NULL, IS NOT NULL | The value is (or is not) null |
| List membership | <value> IN <list> | The list holds the value |
| Label test | n:Person, n:Person|Project, n IS LABELED Person | The node carries the labels; the label expression operators apply |
| Pattern | (p)-[:KNOWS]->(), NOT (p)-[:WORKS_ON]->() | The pattern has (or has no) match from the bound variables |
| Subquery | EXISTS { … } | The subquery returns a row (composing clauses) |
Operators beyond these, and their precedence, are on the expressions page.
Ordering and nulls
| Rule | Behavior |
|---|---|
null in an ascending sort | Last |
null in a descending sort | First |
| Mixed types, ascending | Maps, then nodes, relationships, lists, paths, vectors, points, temporal values, durations, strings, booleans, numbers, and null last |
| Integers and floats | Compared as numbers: 1 < 2.5 < 3 |
| Equal keys | No defined order between them; a second key makes the order stable |
ORDER BY in a UNION | Sorts the last branch only (composing clauses) |
SKIP and LIMIT values
The value is an integer expression that does not depend on the row: a literal, a parameter, or arithmetic on them.
| Value | Result |
|---|---|
| A non-negative integer | Skips or keeps that many rows |
0 | SKIP 0 keeps every row; LIMIT 0 returns none |
| A float literal | syntax error: InvalidArgumentType: SKIP requires an integer value (the LIMIT form names LIMIT) |
| A negative integer | syntax error: NegativeIntegerArgument: LIMIT must not be negative (the SKIP form names SKIP) |
null, or a float at run time | type error: InvalidArgumentType: SKIP/LIMIT must be an integer |
| An expression over a row variable | syntax error: NonConstantExpression: LIMIT must be a non-negative integer constant |
| A parameter not supplied | semantic error: ParameterMissing: Expected parameter(s): <names> |
Errors
| Message | Cause |
|---|---|
syntax error: NoExpressionAlias: expressions in WITH must be aliased with AS | A WITH item that is an expression has no AS |
syntax error: ColumnNameConflict: multiple result columns with the same name '<name>' | Two RETURN items share a name |
syntax error: UndefinedVariable: variable '<name>' not defined | The variable is out of scope: not projected by the previous WITH, or a row variable used in ORDER BY after RETURN DISTINCT or an aggregation without being returned |
syntax error: parse error: expected binding variable in FOR, got "<token>" | FOR written in the UNWIND order |
Under the CLI each of these exits with code 6; the exit code table lists the rest.
Examples
The cells run in page order on one in-memory store, starting from the shared example graph:
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
(grace:Person {name: 'Grace', role: 'engineer'}),
(noor:Person {name: 'Noor', role: 'maintainer'}),
(relate:Project {name: 'relate', language: 'Rust'}),
(tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
(ada)-[:WORKS_ON {since: 2024}]->(relate),
(grace)-[:WORKS_ON {since: 2023}]->(relate),
(grace)-[:WORKS_ON {since: 2022}]->(tck),
(noor)-[:MAINTAINS]->(relate),
(ada)-[:KNOWS]->(grace)(no rows) · nodes created: 5, relationships created: 5, properties set: 13, labels added: 5
OPTIONAL MATCH keeps Noor, who works on no project, and its WHERE drops only Grace's 2022 match:
MATCH (p:Person)
OPTIONAL MATCH (p)-[w:WORKS_ON]->(r:Project) WHERE w.since >= 2023
RETURN p.name AS person, r.name AS project
ORDER BY person, project| person | project |
|---|---|
| "Ada" | "relate" |
| "Grace" | "relate" |
| "Noor" | null |
3 rows
| person | project | |---------|----------| | "Ada" | "relate" | | "Grace" | "relate" | | "Noor" | null | 3 rows
Within one MATCH, the two WORKS_ON relationships must differ, so nobody is paired with themselves:
MATCH (a:Person)-[:WORKS_ON]->(:Project)<-[:WORKS_ON]-(b:Person)
RETURN a.name AS person, b.name AS coworker
ORDER BY person| person | coworker |
|---|---|
| "Ada" | "Grace" |
| "Grace" | "Ada" |
2 rows
| person | coworker | |---------|----------| | "Ada" | "Grace" | | "Grace" | "Ada" | 2 rows
No person has a team property, so both comparisons are null for every row, and WHERE keeps none:
MATCH (p:Person)
WHERE p.team = 'core' OR p.team <> 'core'
RETURN count(p) AS people| people |
|---|
| 0 |
1 row
| people | |--------| | 0 | 1 row
A descending sort puts null first:
MATCH (p:Person)
OPTIONAL MATCH (p)-[w:WORKS_ON]->()
RETURN p.name AS person, w.since AS since
ORDER BY since DESC| person | since |
|---|---|
| "Noor" | null |
| "Ada" | 2024 |
| "Grace" | 2023 |
| "Grace" | 2022 |
4 rows
| person | since | |---------|-------| | "Noor" | null | | "Ada" | 2024 | | "Grace" | 2023 | | "Grace" | 2022 | 4 rows
Values of different types sort by type first:
UNWIND [3, null, 'relate', 2.5, true, [1], {k: 1}] AS value
RETURN value
ORDER BY value| value |
|---|
| {k: 1} |
| [1] |
| "relate" |
| true |
| 2.5 |
| 3 |
| null |
7 rows
| value |
|----------|
| {k: 1} |
| [1] |
| "relate" |
| true |
| 2.5 |
| 3 |
| null |
7 rowsWITH aggregates, and its WHERE filters on the aggregate:
MATCH (p:Person)
WITH p.role AS role, count(*) AS people
WHERE people > 1
RETURN role, people| role | people |
|---|---|
| "engineer" | 2 |
1 row
| role | people | |------------|--------| | "engineer" | 2 | 1 row
SKIP and LIMIT page through a sorted result; a float is refused:
MATCH (p:Person)
RETURN p.name AS name
ORDER BY name
SKIP 1 LIMIT 1| name |
|---|
| "Grace" |
1 row
| name | |---------| | "Grace" | 1 row
MATCH (p:Person) RETURN p.name AS name ORDER BY name SKIP 1.0error: syntax error: InvalidArgumentType: SKIP requires an integer value
ORDER BY and LIMIT also stand as clauses of their own, between MATCH and RETURN:
MATCH (p:Person)
ORDER BY p.name DESC
LIMIT 2
RETURN p.name AS name| name |
|---|
| "Noor" |
| "Grace" |
2 rows
| name | |---------| | "Noor" | | "Grace" | 2 rows
UNWIND gives one row per element: the empty list and null give none, and a string gives one:
UNWIND [[1, 2], [], null, 'solo'] AS item
UNWIND item AS value
RETURN value| value |
|---|
| 1 |
| 2 |
| "solo" |
3 rows
| value | |--------| | 1 | | 2 | | "solo" | 3 rows
LET and FILTER in one pipeline:
UNWIND ['Ada', 'Noor'] AS name
MATCH (p:Person {name: name})
LET projects = COUNT { (p)-[:WORKS_ON]->() }
FILTER projects > 0
RETURN p.name AS name, projects| name | projects |
|---|---|
| "Ada" | 1 |
1 row
| name | projects | |-------|----------| | "Ada" | 1 | 1 row
FOR on the CLI. The WebAssembly build the cells on this page run on rejects it with semantic error: SemanticError: preview feature 'gql-for' is not enabled: set RELATE_PREVIEW=gql-for; the CLI accepts it without an opt-in, and its RELATE_PREVIEW has no gql-for name.
relate --mode table "FOR name IN ['Ada', 'Grace', null] RETURN name"+---------+
| name |
+---------+
| "Ada" |
| "Grace" |
| <null> |
+---------+See also
- Patterns: node, relationship and path patterns, label expressions, and quantifiers.
- Composing clauses:
UNION,CALLsubqueries and subquery expressions. - Writing clauses:
CREATE,MERGE,SETandDELETE. - Expressions: operators,
CASE, comprehensions, and precedence. - Differences from Neo4j: the Neo4j habits that fail in relate.