Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Cypher Guide

A walkthrough of Cypher itself, one clause at a time, building up a small graph as we go. Every example runs as-is in the CLI REPL (marsdb :memory:) or through any language binding — the Cypher text is identical everywhere. For the full list of what’s implemented, see the Cypher Language Reference.

Creating nodes

CREATE (:Person {name: 'Alice', age: 30, city: 'Boston'})
CREATE (:Person {name: 'Bob', age: 27, city: 'Boston'})
CREATE (:Person {name: 'Carol', age: 35, city: 'Seattle'})

:Person is a label; {...} is the node’s property map. A node needs neither — CREATE (n) works — but an unlabeled node is rarely useful.

Creating relationships

A relationship is written inside a pattern, between two nodes:

CREATE (a:Person {name: 'Dave'})-[:KNOWS]->(b:Person {name: 'Eve'})

This creates both nodes and the relationship between them in one statement. That matters: reusing a variable name across two separate CREATE patterns creates two different nodes, not one — CREATE (a:Person {name: 'Alice'}), (a)-[:KNOWS]->(b:Person {name: 'Bob'}) does not mean “connect the Alice created above.” To connect nodes that already exist, match them first:

MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Carol'})
CREATE (a)-[:KNOWS]->(b)
MATCH (c:Person {name: 'Carol'}), (d:Person {name: 'Dave'})
CREATE (c)-[:KNOWS]->(d)

The graph so far: Alice -> Carol -> Dave -> Eve, plus Bob, connected to no one.

Reading data: MATCH and RETURN

MATCH (p:Person) RETURN p.name, p.age
MATCH (p:Person {city: 'Boston'}) RETURN p.name
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name

RETURN p (no property access) returns the whole node; RETURN * returns every variable bound so far. Relationships work the same way as nodes: MATCH ()-[r:KNOWS]->() RETURN r binds the relationship itself.

Filtering: WHERE

MATCH (p:Person) WHERE p.age > 30 RETURN p.name
MATCH (p:Person) WHERE p.name STARTS WITH 'A' RETURN p.name
MATCH (a:Person), (b:Person)
WHERE a.city = b.city AND a.name <> b.name
RETURN a.name, b.name

A pattern itself can be a filter condition:

MATCH (a:Person)
WHERE exists { (a)-[:KNOWS]->(:Person {city: 'Seattle'}) }
RETURN a.name

Traversing further: variable-length paths

[:KNOWS*1..3] matches 1 to 3 KNOWS hops in a row — “friends, friends of friends, up to 3 hops out”:

MATCH (:Person {name: 'Alice'})-[:KNOWS*1..3]->(f:Person)
RETURN DISTINCT f.name

Bounds can be open (*1.., *..3) or omitted entirely (*, unbounded). shortestPath finds the shortest connection between two nodes over a variable-length relationship. Both endpoints must already be bound by an earlier MATCH — a fresh label/property filter can’t be declared inline inside shortestPath(...):

MATCH (a:Person {name: 'Alice'}) MATCH (b:Person {name: 'Eve'})
MATCH p = shortestPath((a)-[:KNOWS*]-(b))
RETURN length(p)

Updating: SET

MATCH (p:Person {name: 'Alice'}) SET p.age = 31
MATCH (p:Person {name: 'Alice'}) SET p += {city: 'Cambridge', verified: true}
MATCH (p:Person {name: 'Alice'}) SET p:VIP

SET p.field = value sets one property. SET p += {...} merges a map in, leaving properties not mentioned untouched. SET p = {...} (no +) replaces the whole property map. SET p:Label adds a label without touching properties.

Create-or-update: MERGE

MERGE matches a pattern if it exists, or creates it if it doesn’t — useful for “insert this node unless it’s already there”:

MERGE (p:Person {name: 'Alice'})
ON CREATE SET p.firstSeen = 'today'
ON MATCH SET p.lastSeen = 'today'

MERGE is capped at one relationship hop per statement (MERGE (a)-[:KNOWS]->(b) is fine; a longer chain in one MERGE isn’t).

Deleting

MATCH (p:Person {name: 'Bob'}) DELETE p

Bob has no relationships, so plain DELETE works. Carol does — deleting her the same way fails:

MATCH (p:Person {name: 'Carol'}) DELETE p
MATCH (p:Person {name: 'Carol'}) DETACH DELETE p

DETACH DELETE removes the attached relationships first, then the node.

Aggregating

MATCH (p:Person) RETURN p.city, count(*) AS people
MATCH (p:Person) RETURN p.city, collect(p.name) AS names
MATCH (:Person)-[:KNOWS]->(f:Person) RETURN count(DISTINCT f) AS unique_friends

Any bare (non-aggregating) expression in the same RETURN becomes an implicit GROUP BY key — there’s no separate GROUP BY clause.

Ordering, paging, and dedup

MATCH (p:Person) RETURN p.name ORDER BY p.age DESC LIMIT 2
MATCH (p:Person) RETURN DISTINCT p.city

Parameters

MATCH (p:Person {name: $name}) RETURN p.age

Send $name as a bound parameter rather than interpolating it into the query text — see the per-language binding pages (Rust, Python, Go) for exactly how to pass parameters from each.

Where to go next