Docs
View as markdown

Patterns

Node and relationship patterns, label expressions, variable-length and quantified paths, path selectors and modes, and the hop bound limit in relate.

cyphercli

A pattern is the shape a statement looks for or creates. A node An entity in the graph: it carries zero or more labels and a map of properties, and relationships connect it to other nodes. Read more in the glossary pattern sits in parentheses, a relationship A directed, typed connection between two nodes, with properties of its own; every relationship has exactly one type and a start and an end node. Read more in the glossary pattern in brackets between two nodes, and a chain of them is a path An alternating sequence of nodes and relationships that a pattern matched, bound with p = … and read with nodes(p), relationships(p) and length(p). Read more in the glossary pattern. MATCH, OPTIONAL MATCH, MERGE, CREATE, pattern predicates and EXISTS and COUNT subqueries all take patterns; CREATE and MERGE accept only fixed-length ones. relate accepts the GQL forms of Cypher 25: label expressions, quantified path patterns, path selectors and path modes, with no preview opt-in.

Synopsis

Node:          (<variable> <label expression> {<key>: <value>, …} WHERE <predicate>)
Relationship:  -[<variable> <type expression> <length> {<key>: <value>, …} WHERE <predicate>]->
               <-[…]-   -[…]-   <-[…]->   -->   <--   --
Length:        *   *<n>   *<n>..<m>   *<n>..   *..<m>
Quantified:    (<path pattern>)<quantifier>     -[…]-><quantifier>
Quantifier:    {<n>}   {<n>,<m>}   {<n>,}   {,<m>}   +   *
Path:          <path variable> = [<selector>] [<path mode>] <path pattern>
Match mode:    MATCH [DIFFERENT RELATIONSHIPS | REPEATABLE ELEMENTS] <path pattern>, …
Selector:      ANY SHORTEST | ALL SHORTEST | SHORTEST <k> | SHORTEST <k> GROUPS | ANY | ANY <k>
Path mode:     WALK | TRAIL | ACYCLIC
Functions:     shortestPath(<pattern>)   allShortestPaths(<pattern>)

Every part inside the parentheses and brackets is optional.

Node and relationship patterns

PartNodeRelationship
Variable(p) binds the node to p[r] binds the relationship to r; with a length, r is a list
Labels or type(p:Person), or a label expression[:WORKS_ON], or a type expression
Property map(p {name: 'Ada'}): equality on each key[w {since: 2024}]: the same
Inline WHERE(p:Person WHERE p.role = 'engineer')[w:WORKS_ON WHERE w.since < 2024]
Parameters(p {name: $name})[w {since: $year}]
DirectionMatches
-[…]->, -->Left to right
<-[…]-, <--Right to left
-[…]-, --, <-[…]->, <-->Either direction

In CREATE, a relationship needs a direction and exactly one type (writing clauses). In CREATE and MERGE, a variable-length or quantified relationship fails with syntax error: CreatingVarLength: variable length relationships cannot be used in CREATE; the MERGE form names MERGE.

Uniqueness

Within one MATCH, a relationship appears at most once per row: two relationship patterns never bind the same relationship. This is the DIFFERENT RELATIONSHIPS match mode, the default. Separate MATCH clauses do not share the rule. REPEATABLE ELEMENTS lets one relationship appear several times in a row; it needs every quantifier bounded, otherwise syntax error: parse error: UnsafeRepeatableElements: REPEATABLE ELEMENTS cannot be combined with an unbounded quantified path pattern.

Label expressions

A label expression tests a node's label A name that classifies a node, such as Person; a node has zero or more, and patterns, indexes and constraints refer to them. Read more in the glossary; the same operators on a relationship test its type.

FormNode matches whenOn a relationship
:A:BIt has A and B—
:A&BIt has A and BNever matches: a relationship has one type
:A|BIt has A or BThe type is A or B
:!AIt does not have AThe type is not A
:%It has at least one labelAny type
:!%It has no label—
:(A|B)&!CParentheses groupThe same
IS A, IS A|BThe same as :The same
:$(<expression>), :$all(<expression>)It has every label the string or list namesThe type is the one named
:$any(<expression>)It has any label the list namesThe type is one of those named
n:A|B, n IS LABELED A in WHEREThe same test as a predicate—
Mixing ruleResult
:A:B and a symbol (|, &, !, %) in one label expressionsyntax error: parse error: InvalidLabelExpression: cannot mix colon-conjunction with label expression symbols; with RELATE_PREVIEW=mixed-label-syntax it still fails, and the message ends … within one label expression
:A:B in one pattern and a symbol in another pattern of the same clausesyntax error: parse error: InvalidLabelExpression: mixing label expression symbols (|, &, !, and %) with colon (:) between labels is not allowed; RELATE_PREVIEW=mixed-label-syntax allows it
IS and :A:B in one clausesyntax error: parse error: MixedIsAndColonConjunction: cannot mix the IS introducer with colon-conjunction within the same clause; RELATE_PREVIEW=mixed-label-syntax allows it

Variable-length relationships

The classic form puts a length after the type.

LengthHops
*1 or more
*<n>Exactly n
*<n>..<m>n to m
*<n>..n or more
*..<m>1 to m
*0..<m>0 to m: the start node itself is a match

The relationship variable of a variable-length pattern is a list of relationships. A property map applies to every relationship in the list.

Quantified path patterns

A quantified path pattern A pattern repeated a bounded or unbounded number of times, written ((a)-[:R]->(b)){1,3} or -[:R]->{1,3}; GQL's form of a variable-length path. Read more in the glossary repeats a parenthesized pattern, ((a)-[r:KNOWS]->(b)){1,3}, or a single relationship, -[:KNOWS]->{1,3}.

QuantifierRepetitions
{<n>}Exactly n
{<n>,<m>}n to m
{<n>,}n or more
{,<m>}0 to m
+1 or more
*0 or more

Variables inside the repeated part are group variables: after the match, each is a list with one entry per repetition. An inline WHERE inside the repeated part applies to every repetition.

Path selectors and modes

A selector keeps some of the paths between each pair of end nodes; a mode restricts what one path may repeat.

SelectorKeeps, per pair of end nodes
ANY SHORTESTOne shortest path
ALL SHORTESTEvery path of the shortest length
SHORTEST <k>The k shortest paths
SHORTEST <k> GROUPSEvery path in each of the k shortest lengths
ANYOne path
ANY <k>k paths
ModeA path may not repeat
WALKAnything beyond what the match mode forbids
TRAILA relationship
ACYCLICA node, so it never returns to its start

shortestPath(<pattern>) and allShortestPaths(<pattern>) are the older forms. In MATCH p = shortestPath(…) with no upper bound, the search has no hop cap. As an expression, shortestPath(…) with no upper bound finds paths of up to 32 hops and returns null for a longer one.

Path variables

p = <pattern> binds the matched path. nodes(p) and relationships(p) return its parts as lists and length(p) its number of relationships; the functions page has the rest.

Hop bound limit

An explicit upper bound greater than the traversal-depth limit fails before the statement runs. The limit is a safety limit that protects the process stack, not a database limit.

SettingValue
Default limit500 hops
Applies toAn explicit upper bound: *1..<n>, *..<n>, *<n>, {1,<n>}, {<n>}
ExemptAn unbounded upper bound (*, *1.., +, {1,}), including inside shortestPath(…) and allShortestPaths(…). An explicit bound inside them, or under a SHORTEST selector, is checked like any other
Raised by--max-traversal-depth <n> or RELATE_MAX_TRAVERSAL_DEPTH, from 1 to 2000 (flags)
Over the limitsyntax error: TraversalDepthExceeded: This query allows paths up to <n> hops. This is a safety limit protecting against excessive traversal (not a database limit) — lower the path bound, or raise it with --max-traversal-depth (up to 2000)., where <n> is the statement's own bound; CLI exit code 6
Flag out of rangeError: invalid --max-traversal-depth: must be between 1 and 2000 (exit code 2)

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

Four label expressions, counted:

Cypher
RETURN COUNT { (n:Person|Project) } AS personOrProject,
       COUNT { (n:!Person) } AS notPerson,
       COUNT { (n:%) } AS anyLabel,
       COUNT { (n IS Person) } AS isPerson
Recorded result
personOrProjectnotPersonanyLabelisPerson
5253

1 row

A type expression: every relationship from Grace except KNOWS:

Cypher
MATCH (:Person {name: 'Grace'})-[r:!KNOWS]->(x)
RETURN type(r) AS type, x.name AS target ORDER BY target
Recorded result
typetarget
"WORKS_ON""opencypher-tck"
"WORKS_ON""relate"

2 rows

Labels from a parameter; $any matches either label:

Cypher
MATCH (n:$any($labels)) RETURN count(n) AS nodes
Parameters
Recorded result
nodes
5

1 row

Colon conjunction and a symbol in one label expression:

Cypher
MATCH (n:Person:Project|Bot) RETURN count(n) AS nodes
Expected error
error: syntax error: parse error: InvalidLabelExpression: cannot mix colon-conjunction with label expression symbols
  MATCH (n:Person:Project|Bot) RETURN count(n) AS nodes
                         ^

Inline WHERE on a node and on a relationship:

Cypher
MATCH (p:Person WHERE p.role = 'engineer')-[w:WORKS_ON WHERE w.since < 2024]->(r:Project)
RETURN p.name AS person, r.name AS project ORDER BY project
Recorded result
personproject
"Grace""opencypher-tck"
"Grace""relate"

2 rows

A variable-length relationship binds a list:

Cypher
MATCH (:Person {name: 'Ada'})-[r:KNOWS|WORKS_ON*1..2]->(x)
RETURN [rel IN r | type(rel)] AS types, x.name AS reached
ORDER BY size(types), reached
Recorded result
typesreached
["KNOWS"]"Grace"
["WORKS_ON"]"relate"
["KNOWS", "WORKS_ON"]"opencypher-tck"
["KNOWS", "WORKS_ON"]"relate"

4 rows

A variable-length pattern follows a hierarchy to any depth. This cell writes a small package tree and returns every dependency path from app. The graph view draws the result in layers, app at the top and each package one layer below the deepest package that depends on it; its Layout menu switches to the force and circular layouts:

Cypher
CREATE (app:Package {name: 'app'}),
       (cli:Package {name: 'cli'}),
       (engine:Package {name: 'engine'}),
       (parser:Package {name: 'parser'}),
       (storage:Package {name: 'storage'}),
       (lexer:Package {name: 'lexer'}),
       (app)-[:DEPENDS_ON]->(cli),
       (app)-[:DEPENDS_ON]->(engine),
       (cli)-[:DEPENDS_ON]->(parser),
       (engine)-[:DEPENDS_ON]->(parser),
       (engine)-[:DEPENDS_ON]->(storage),
       (parser)-[:DEPENDS_ON]->(lexer);
MATCH p = (:Package {name: 'app'})-[:DEPENDS_ON*]->(:Package)
RETURN p
Recorded result
// 7 rows · nodes created: 6, relationships created: 6, properties set: 6, labels added: 6
(n5:Package {name: "app"})-[r6:DEPENDS_ON]->(n7:Package {name: "engine"})
(n5)-[r6]->(n7)-[r9:DEPENDS_ON]->(n9:Package {name: "storage"})
(n5)-[r6]->(n7)-[r8:DEPENDS_ON]->(n8:Package {name: "parser"})
(n5)-[r6]->(n7)-[r8]->(n8)-[r10:DEPENDS_ON]->(n10:Package {name: "lexer"})
(n5)-[r5:DEPENDS_ON]->(n6:Package {name: "cli"})
(n5)-[r5]->(n6)-[r7:DEPENDS_ON]->(n8)
(n5)-[r5]->(n6)-[r7]->(n8)-[r10]->(n10)
The graph this cell returns, under "Examples"app DEPENDS_ON engine; engine DEPENDS_ON storage; engine DEPENDS_ON parser; parser DEPENDS_ON lexer; app DEPENDS_ON cli; cli DEPENDS_ON parserapp:Packageengine:Packagestorage:Packageparser:Packagelexer:Packagecli:PackageDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ON
The graph this cell returns, under "Examples"app DEPENDS_ON engine; engine DEPENDS_ON storage; engine DEPENDS_ON parser; parser DEPENDS_ON lexer; app DEPENDS_ON cli; cli DEPENDS_ON parserapp:Packageengine:Packagestorage:Packageparser:Packagelexer:Packagecli:PackageDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ONDEPENDS_ON
The graph this cell returns, under "Examples"

The seven rows are seven paths. parser and lexer are reached two ways, through cli and through engine, so they appear in more than one path but once in the graph.

Group variables of a quantified path pattern are lists, one entry per repetition:

Cypher
MATCH (:Person {name: 'Ada'}) ((a)-[r]->(b)){2} (end)
RETURN [x IN a | x.name] AS from, [rel IN r | type(rel)] AS via, end.name AS reached
ORDER BY reached
Recorded result
fromviareached
["Ada", "Grace"]["KNOWS", "WORKS_ON"]"opencypher-tck"
["Ada", "Grace"]["KNOWS", "WORKS_ON"]"relate"

2 rows

The two shortest routes from Noor to Grace, in either direction:

Cypher
MATCH p = SHORTEST 2 (:Person {name: 'Noor'})-[]-+(:Person {name: 'Grace'})
RETURN [n IN nodes(p) | n.name] AS route, length(p) AS hops
ORDER BY hops
Recorded result
routehops
["Noor", "relate", "Grace"]2
["Noor", "relate", "Ada", "Grace"]3

2 rows

A path variable binds what the pattern matched. This cell returns the path from Ada through Grace to opencypher-tck as p, and the people graph beside it as people. The graph view draws the path's three nodes and two relationships on top and dims the rest, and its legend names the path:

Cypher
MATCH p = (:Person {name: 'Ada'})-[:KNOWS]->(:Person)-[:WORKS_ON]->(:Project {name: 'opencypher-tck'})
MATCH g = (:Person)-->()
RETURN p, collect(g) AS people
Recorded result
// 1 row
[ | (n0:Person {name: "Ada", role: "engineer"})-[r4:KNOWS]->(n1:Person {name: "Grace", role: "engineer"})-[r2:WORKS_ON {since: 2022}]->(n4:Project {language: "Gherkin", name: "opencypher-tck"}), [ | (n0)-[r0:WORKS_ON {since: 2024}]->(n3:Project {language: "Rust", name: "relate"}), (n0)-[r4]->(n1), (n1)-[r2]->(n4), (n1)-[r1:WORKS_ON {since: 2023}]->(n3), (n2:Person {name: "Noor", role: "maintainer"})-[r3:MAINTAINS]->(n3)]]
The graph this cell returns, under "Examples"Ada KNOWS Grace; Grace WORKS_ON opencypher-tck; Ada WORKS_ON relate; Grace WORKS_ON relate; Noor MAINTAINS relate. Highlighted path: Ada, Grace, opencypher-tckrelate:ProjectNoor:PersonAda:PersonGrace:Personopencypher-tck:ProjectWORKS_ONWORKS_ONMAINTAINSKNOWSWORKS_ON
The graph this cell returns, under "Examples"Ada KNOWS Grace; Grace WORKS_ON opencypher-tck; Ada WORKS_ON relate; Grace WORKS_ON relate; Noor MAINTAINS relate. Highlighted path: Ada, Grace, opencypher-tckrelate:ProjectNoor:PersonAda:PersonGrace:Personopencypher-tck:ProjectWORKS_ONWORKS_ONMAINTAINSKNOWSWORKS_ON
The graph this cell returns, under "Examples"

shortestPath binds one shortest path. From Noor to Grace in either direction it runs through relate, two hops; the route through Ada takes three:

Cypher
MATCH p = shortestPath((:Person {name: 'Noor'})-[*]-(:Person {name: 'Grace'}))
MATCH g = (:Person)-->()
RETURN p, length(p) AS hops, collect(g) AS people
Recorded result
// 1 row
[{hops: 2} | (n2:Person {name: "Noor", role: "maintainer"})-[r3:MAINTAINS]->(n3:Project {language: "Rust", name: "relate"})<-[r1:WORKS_ON {since: 2023}]-(n1:Person {name: "Grace", role: "engineer"}), [ | (n0:Person {name: "Ada", role: "engineer"})-[r0:WORKS_ON {since: 2024}]->(n3), (n0)-[r4:KNOWS]->(n1), (n1)-[r2:WORKS_ON {since: 2022}]->(n4:Project {language: "Gherkin", name: "opencypher-tck"}), (n1)-[r1]->(n3), (n2)-[r3]->(n3)]]
The graph this cell returns, under "Examples"Noor MAINTAINS relate; Grace WORKS_ON relate; Ada WORKS_ON relate; Ada KNOWS Grace; Grace WORKS_ON opencypher-tck. Highlighted path: Noor, relate, GraceAda:Personopencypher-tck:ProjectNoor:Personrelate:ProjectGrace:PersonWORKS_ONKNOWSWORKS_ONMAINTAINSWORKS_ON
The graph this cell returns, under "Examples"Noor MAINTAINS relate; Grace WORKS_ON relate; Ada WORKS_ON relate; Ada KNOWS Grace; Grace WORKS_ON opencypher-tck. Highlighted path: Noor, relate, GraceAda:Personopencypher-tck:ProjectNoor:Personrelate:ProjectGrace:PersonWORKS_ONKNOWSWORKS_ONMAINTAINSWORKS_ON
The graph this cell returns, under "Examples"

Only a column of type PATH is highlighted: people is a list of paths too, and stays dimmed. Without a PATH column, every path in the rows is highlighted. Every graph view with a path in its result has the Highlight path button; these two cells start with it on.

TRAIL allows a path back to Ada around the Ada, Grace and relate triangle; ACYCLIC does not:

Cypher
RETURN COUNT { MATCH p = TRAIL (a:Person {name: 'Ada'})-[]-+(a) RETURN p } AS trail,
       COUNT { MATCH p = ACYCLIC (a:Person {name: 'Ada'})-[]-+(a) RETURN p } AS acyclic
Recorded result
trailacyclic
20

1 row

REPEATABLE ELEMENTS lets a path reuse a relationship, so more paths of up to four hops connect Noor and Grace:

Cypher
RETURN COUNT { MATCH p = (:Person {name: 'Noor'})-[]-{1,4}(:Person {name: 'Grace'}) RETURN p } AS differentRelationships,
       COUNT { MATCH REPEATABLE ELEMENTS p = (:Person {name: 'Noor'})-[]-{1,4}(:Person {name: 'Grace'}) RETURN p } AS repeatableElements
Recorded result
differentRelationshipsrepeatableElements
27

1 row

An explicit bound over the default limit of 500 hops fails before the statement runs:

Cypher
MATCH (:Person)-[:KNOWS*1..1000]->(b) RETURN count(b) AS reached
Expected error
error: syntax error: TraversalDepthExceeded: This query allows paths up to 1,000 hops. This is a safety limit protecting against excessive traversal (not a database limit) — lower the path bound, or raise it with --max-traversal-depth (up to 2000).

A line of 40 stops shows the two unbounded cases: MATCH walks all 39 hops, and the shortestPath expression stops at 32. This cell writes 40 Stop nodes and the 39 NEXT relationships between them:

Cypher
UNWIND range(1, 40) AS i CREATE (:Stop {i: i});
MATCH (a:Stop), (b:Stop) WHERE b.i = a.i + 1 CREATE (a)-[:NEXT]->(b);
MATCH (first:Stop {i: 1})
OPTIONAL MATCH p = (first)-[:NEXT*]->(:Stop)
RETURN max(length(p)) AS longestWalk,
       length(shortestPath((first)-[:NEXT*]->(:Stop {i: 33}))) AS hopsTo33,
       length(shortestPath((first)-[:NEXT*]->(:Stop {i: 34}))) AS hopsTo34
Recorded result
longestWalkhopsTo33hopsTo34
3932null

1 row · nodes created: 40, relationships created: 39, properties set: 40, labels added: 40

On the CLI, --max-traversal-depth raises the limit for one run. The store is in memory and empty, so no path matches:

relate --max-traversal-depth 1000 --mode table "MATCH (:Person)-[:KNOWS*1..1000]->(b) RETURN count(b) AS reached"
+---------+
| reached |
+---------+
| 0       |
+---------+

See also