Docs
View as markdown

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

ClauseTakesProducesNotes
MATCHOne or more patternsOne row per way the patterns match; no match, no rowsComma-separated patterns form a cartesian product, joined on shared variables. Within one MATCH, each relationship is matched at most once per row
OPTIONAL MATCHOne or more patternsThe input row with the new variables bound, or bound to null when nothing matchesIts WHERE filters the optional part only; a row whose optional part fails the filter is kept with nulls
WHEREA predicateThe rows where it is trueA row where the predicate is null is dropped, the same as false. Follows MATCH, OPTIONAL MATCH or WITH
WITHProjection itemsA new row per input row, holding only the projected variablesEvery expression needs AS; a bare variable does not. Variables not projected go out of scope
RETURNProjection itemsThe result columnsA column without AS is named after its expression text, such as p.name. Two columns with one name fail
ORDER BYSort keysThe rows sortedASC 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, OFFSETA non-negative integerAll but the first n rowsOFFSET is the GQL spelling of SKIP. Also a clause of its own between other clauses, as in GQL
LIMITA non-negative integerAt most n rowsLIMIT 0 returns no rows. Also a clause of its own between other clauses, as in GQL
UNWINDA list and a variable nameOne row per element[] and null give no rows; a value that is not a list gives one row
FORA variable name and a listThe same rows as UNWINDGQL 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> pairsThe input row with new variablesA pair cannot refer to another pair of the same LET; a second LET can refer to the first
FILTERA predicateThe rows where it is trueA clause of its own; WHERE after it is optional

Projection

FormResult
RETURN *, WITH *Every variable in scope, as one column each
RETURN DISTINCT, WITH DISTINCTDuplicate rows removed
RETURN ALLEvery row kept; the default, written out
Aggregate beside plain itemsPlain 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 ALLEvery plain item is a key, as without GROUP BY

WHERE predicates

PredicateFormtrue when
Comparison=, <>, <, >, <=, >=The comparison holds; a comparison with null is null
BooleanAND, OR, XOR, NOTThree-valued: null AND false is false, null OR true is true, otherwise null propagates
StringSTARTS WITH, ENDS WITH, CONTAINSCase-sensitive match on strings
Regular expression<string> =~ '<regex>'The whole string matches
Null testIS NULL, IS NOT NULLThe value is (or is not) null
List membership<value> IN <list>The list holds the value
Label testn:Person, n:Person|Project, n IS LABELED PersonThe 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
SubqueryEXISTS { … }The subquery returns a row (composing clauses)

Operators beyond these, and their precedence, are on the expressions page.

Ordering and nulls

RuleBehavior
null in an ascending sortLast
null in a descending sortFirst
Mixed types, ascendingMaps, then nodes, relationships, lists, paths, vectors, points, temporal values, durations, strings, booleans, numbers, and null last
Integers and floatsCompared as numbers: 1 < 2.5 < 3
Equal keysNo defined order between them; a second key makes the order stable
ORDER BY in a UNIONSorts 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.

ValueResult
A non-negative integerSkips or keeps that many rows
0SKIP 0 keeps every row; LIMIT 0 returns none
A float literalsyntax error: InvalidArgumentType: SKIP requires an integer value (the LIMIT form names LIMIT)
A negative integersyntax error: NegativeIntegerArgument: LIMIT must not be negative (the SKIP form names SKIP)
null, or a float at run timetype error: InvalidArgumentType: SKIP/LIMIT must be an integer
An expression over a row variablesyntax error: NonConstantExpression: LIMIT must be a non-negative integer constant
A parameter not suppliedsemantic error: ParameterMissing: Expected parameter(s): <names>

Errors

MessageCause
syntax error: NoExpressionAlias: expressions in WITH must be aliased with ASA 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 definedThe 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:

Cypher
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)
Recorded result
(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:

Cypher
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
Recorded result
personproject
"Ada""relate"
"Grace""relate"
"Noor"null

3 rows

Within one MATCH, the two WORKS_ON relationships must differ, so nobody is paired with themselves:

Cypher
MATCH (a:Person)-[:WORKS_ON]->(:Project)<-[:WORKS_ON]-(b:Person)
RETURN a.name AS person, b.name AS coworker
ORDER BY person
Recorded result
personcoworker
"Ada""Grace"
"Grace""Ada"

2 rows

No person has a team property, so both comparisons are null for every row, and WHERE keeps none:

Cypher
MATCH (p:Person)
WHERE p.team = 'core' OR p.team <> 'core'
RETURN count(p) AS people
Recorded result
people
0

1 row

A descending sort puts null first:

Cypher
MATCH (p:Person)
OPTIONAL MATCH (p)-[w:WORKS_ON]->()
RETURN p.name AS person, w.since AS since
ORDER BY since DESC
Recorded result
personsince
"Noor"null
"Ada"2024
"Grace"2023
"Grace"2022

4 rows

Values of different types sort by type first:

Cypher
UNWIND [3, null, 'relate', 2.5, true, [1], {k: 1}] AS value
RETURN value
ORDER BY value
Recorded result
value
{k: 1}
[1]
"relate"
true
2.5
3
null

7 rows

WITH aggregates, and its WHERE filters on the aggregate:

Cypher
MATCH (p:Person)
WITH p.role AS role, count(*) AS people
WHERE people > 1
RETURN role, people
Recorded result
rolepeople
"engineer"2

1 row

SKIP and LIMIT page through a sorted result; a float is refused:

Cypher
MATCH (p:Person)
RETURN p.name AS name
ORDER BY name
SKIP 1 LIMIT 1
Recorded result
name
"Grace"

1 row

Cypher
MATCH (p:Person) RETURN p.name AS name ORDER BY name SKIP 1.0
Expected error
error: syntax error: InvalidArgumentType: SKIP requires an integer value

ORDER BY and LIMIT also stand as clauses of their own, between MATCH and RETURN:

Cypher
MATCH (p:Person)
ORDER BY p.name DESC
LIMIT 2
RETURN p.name AS name
Recorded result
name
"Noor"
"Grace"

2 rows

UNWIND gives one row per element: the empty list and null give none, and a string gives one:

Cypher
UNWIND [[1, 2], [], null, 'solo'] AS item
UNWIND item AS value
RETURN value
Recorded result
value
1
2
"solo"

3 rows

LET and FILTER in one pipeline:

Cypher
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
Recorded result
nameprojects
"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