Each section links to the page that explains it in full.
MATCH: pattern matching
MATCH (n)
RETURN n
MATCH (n:Person {name: 'Alice'})
RETURN n.age
MATCH (a:Person)-->(b:Company)
RETURN a.name, b.name
MATCH (a:Person)-[e:KNOWS {since: 2010}]->(b:Person)
RETURN a.name, e.since, b.name
MATCH (a)-[r:KNOWS|WORKS_AT]->(b)
RETURN a.name, type(r), b.name
MATCH (a:Person)-[e]-(b)
RETURN a.name, b.name
MATCH (a:Person WHERE a.age > 30)-[r:KNOWS WHERE r.since > 2012]->(b)
RETURN b.name
MATCH (n:Person)
ORDER BY n.age LIMIT 2
RETURN n.name()is a node,[]is an edge,-ignores direction.- Node labels use
:Labeland can be stacked:(n:Person:Employee). - Edge types use
:EDGE_TYPE:-[:KNOWS]->. An edge has exactly one type, and[:A|B]matches either. RETURN ngives the internal node ID. Return properties explicitly:RETURN n.name.- Unknown labels and properties are forgiving: they match nothing or read as
null.
OPTIONAL MATCH
MATCH (a:Person)
OPTIONAL MATCH (a)-[:WORKS_AT]->(c:Company)
RETURN a.name, c.name
MATCH (a:Person)
OPTIONAL MATCH (a)-[:KNOWS]->(b)
RETURN a.name, count(b)WHERE: filter
MATCH (n)
WHERE n:Person
RETURN n.name
MATCH (n:Person)
WHERE n.age >= 18 AND n.city = 'London'
RETURN n.name
MATCH (n:Person)
WHERE n.name STARTS
WITH 'A' OR n.name CONTAINS 'ro'
RETURN n.name
MATCH (n:Person)
WHERE n.name IN ['Alice', 'Bob']
RETURN n.name
MATCH (n:Person)
WHERE 'admin' IN n.tags
RETURN n.name
MATCH (n:Person)
WHERE n.tags IS NOT NULL
RETURN n.name
MATCH (n)-[e]->(m)
WHERE e:KNOWS AND e.since > 2012
RETURN n.name, m.name
MATCH (p:Person)
WHERE NOT (p)-[:KNOWS]->()
RETURN p.name- Comparison:
= <> != < <= > >=, null testsIS NULL/IS NOT NULL, booleanAND OR XOR NOT. - Strings and lists:
IN,STARTS WITH,ENDS WITH,CONTAINS, concatenation with+or||. - Use
IS NOT NULLto test for a property (=~andexists(n.prop)are not yet supported). - For Doubles, use ranges (
n.score > 1.99 AND n.score < 2.01) or convert withtoInteger.
Lookup by native node ID
MATCH (n)
WHERE n = 0
RETURN n.name
MATCH (n)-[:KNOWS]->(m)
WHERE n = 0
RETURN m.name
UNWIND [0, 1, 2] AS x
MATCH (n)
WHERE n = x
RETURN id(n), n.name
EXPLAIN MATCH (n)
WHERE n = 0
RETURN n.name- Compare the variable (
n = 0), notid(n) = 0. The first is a direct seek and the second scans every node. - For batches,
UNWINDa literal list of distinct IDs and returnid(n)rather thanx.
Joins and Cartesian products
MATCH (p:Person), (c:Company)
RETURN p.name, c.name
MATCH (a:Person)-[:KNOWS]->(b), (b)-[:WORKS_AT]->(c)
RETURN a.name, b.name, c.name
MATCH (a:Person)-->(i:Interest)<--(b:Person)
WHERE a <> b
RETURN a.name, b.name, i.name
MATCH (a:Person), (b:Person)
WHERE a.city = b.city AND a <> b
RETURN a.name, b.name- A comma separates independent patterns. Shared variables create joins, unrelated patterns create Cartesian products (N × M rows).
Variable-length and quantified paths
MATCH (a:Person {name: 'Alice'})-[:KNOWS]->+(b)
RETURN DISTINCT b.name
MATCH (a:Person {name: 'Alice'})-[:KNOWS]->*(b)
RETURN DISTINCT b.name
MATCH (a:Person {name: 'Alice'})-[:KNOWS]->{1,2}(b)
RETURN DISTINCT b.name
MATCH (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(b)
RETURN DISTINCT b.name
MATCH (a:Person {name: 'Alice'}) ((x)-[:KNOWS]->(y) WHERE y.age > x.age)+ (b)
RETURN b.name
MATCH p = (a:Person {name: 'Alice'})-[:KNOWS]->+(b)
RETURN [n IN nodes(p) | n.name], length(p)- Postfix
->*is 0 or more hops. Bracket-[:KNOWS*]->is 1 or more. - Each edge is used at most once per path. Bound the length on large graphs.
WITH, ORDER BY, SKIP, LIMIT, UNION
MATCH (n:Person)
WITH n.city AS city, count(*) AS c
WHERE c > 1
RETURN city, c
MATCH (n:Person)
RETURN DISTINCT n.city
MATCH (n:Person)
RETURN n.name, n.age
ORDER BY n.age DESC SKIP 1 LIMIT 2
MATCH (p:Person)
RETURN p.name AS name
UNION
MATCH (c:Company)
RETURN c.name AS name- Expressions in
WITHmust be aliased. - Nulls sort last ascending and first descending.
Aggregation
MATCH (n:Person)
RETURN count(*), count(n.age), sum(n.age), avg(n.age), min(n.age), max(n.age)
MATCH (a:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name, count(a) AS employees, collect(a.name) AS who
MATCH (n:Person)
RETURN count(DISTINCT n.city)
MATCH ()-[r]->()
RETURN type(r) AS t, count(*) AS c
ORDER BY c DESC- Grouping is implicit. To filter on an aggregate, compute it in a
WITHand filter afterwards. stdevandpercentile*aggregates are not yet available.
UNWIND, lists, CASE
UNWIND [1, 2, 3] AS x
RETURN x * 10
MATCH (n:Person)
WITH collect(n.name) AS names
UNWIND names AS nm
RETURN nm
MATCH (n:Person)
UNWIND n.tags AS t
RETURN t, count(*)
RETURN [x IN range(1, 6) WHERE x % 2 = 0 | x * x]
MATCH (a:Person)
RETURN a.name, [(a)-[:KNOWS]->(b) | b.name] AS friends
MATCH (n:Person)
WHERE any(t IN n.tags WHERE t = 'vip')
RETURN n.name
RETURN reduce(total = 0, x IN [1, 2, 3] | total + x)
MATCH (n:Person)
RETURN n.name, CASE WHEN n.age >= 35 THEN 'senior' ELSE 'junior' END AS bandSubqueries
MATCH (p:Person)
WHERE EXISTS { (p)-[:WORKS_AT]->(:Company {name: 'Acme'}) }
RETURN p.name
MATCH (p:Person)
RETURN p.name, COUNT { (p)-[:KNOWS]->() } AS outDegree
MATCH (p:Person)
CALL (p) {
MATCH (p)-[:KNOWS]->(f)
RETURN f.name AS friend LIMIT 1
}
RETURN p.name, friend
MATCH (p:Person)
OPTIONAL CALL (p) {
MATCH (p)-[:WORKS_AT]->(c)
RETURN c.name AS co
}
RETURN p.name, coFunctions
| Category | Functions |
|---|---|
| Entity | id, labels, type, startNode, endNode |
| Path and list | nodes, relationships, length, size, head, last, tail, reverse, range, reduce |
| Conversion | toInteger, toFloat, toString, toBoolean, coalesce |
| String | toUpper, toLower, trim, ltrim, rtrim, reverse |
| Math | abs, sign, ceil, floor, round, sqrt, exp, log, log10, pi, e, sin, cos, tan |
| Temporal | datetime, duration |
| Embedding | cosine_similarity, euclidean_distance |
| Aggregate | count, collect, sum, avg, min, max |
MATCH (n)-[e]->(m)
RETURN labels(n), type(e), labels(m)
RETURN datetime('2024-03-15T10:30:00Z') + duration({days: 1})
MATCH (n:Document)
RETURN n.title, cosine_similarity(n.emb, (0.4, 0.3, 0.8, 0.1))- Function names are case-sensitive, except
countandcollect. - Use
type(e)for an edge’s type (it replacesedgeType()).
Data types
| Type | Example |
|---|---|
| String | 'hello' or "hello" |
| Integer | 30, 0x1F |
| Double | 3.14 |
| Boolean | true |
| List | [1, 'a', [2]] |
| Map | {a: 1} |
| DateTime | datetime('2024-03-15T10:30:00Z') |
| Duration | duration({days: 2}) |
| Embedding | (1.2, 2.0, 0.0) (parentheses, not brackets) |
Writing (inside a change)
CHANGE NEW
CREATE (:Person {name: 'Alice', age: 30})
CREATE (:Person {name: 'Mick'})-[:FRIEND_OF {since: 2020}]->(:Person {name: 'John'})
COMMIT
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Mick'})
CREATE (a)-[:KNOWS]->(b)
MERGE (n:Person {name: 'Alice'})
ON CREATE SET n.created = true
ON MATCH SET n.seen = true
MATCH (n:Person {name: 'Alice'})
SET n.age = 31, n.position = 'Developer'
MATCH (n:Person {name: 'Alice'})
REMOVE n.position
MATCH (n:Person {name: 'Alice'})
DETACH DELETE n
MATCH (n:Person)-[e:KNOWS]->(m)
DELETE e
CHANGE SUBMIT- Every node needs at least one label and every edge exactly one type. Labels are set at creation.
- Run
COMMITafter creating entities that a later query must match.CHANGE SUBMITcommits for you. - In the Python SDK, call
client.checkout()afterCHANGE SUBMIT. - For more than a few thousand entities, use
LOAD PARQUET.
Procedures
| Query | Description |
|---|---|
CALL db.labels() | All node labels |
CALL db.edgeTypes() | All edge types |
CALL db.propertyTypes() | All property keys and their types |
CALL db.history() | Commit history |
CALL db.describeCommit('<hash>') | Counts for a single commit |
CALL db.getNodes([0, 1]) | Labels and all properties of specific nodes |
CALL db.procedures() | All procedures and signatures |
Outputs can be chained: CALL db.labels() YIELD label MATCH (n) WHERE label IN labels(n) RETURN label, count(n).
Graph algorithms and search
MATCH (a:Station {name: 'Ashchurch'}), (b:Station {name: 'Pershore'})
shortestPath(a, b, distance, dist, path)
RETURN dist, path
VECTOR SEARCH IN my_index FOR 5 (1.2, 0.5, 3.0, 0.1) YIELD ids, score
RETURN ids, scoreSee Shortest path and Vector search.
Engine commands
| Command | Description |
|---|---|
CREATE GRAPH <name> | Create a new named graph |
LOAD GRAPH <name> | Load an existing graph |
LOAD GML 'f.gml' AS g | Load a GML file as a graph (file in <turing-dir>/data) |
LOAD JSONL 'f.jsonl' AS g | Load a JSONL file as a graph, optionally WITH EMBEDDINGS, WITH DATETIMES, WITH DURATIONS |
LOAD PARQUET '<dir>' AS g | Load Parquet files as a graph |
LOAD CSV 'f.csv' [WITH HEADERS] [ON ERROR SKIP|FAIL] AS row | Stream rows from a CSV file |
LIST GRAPH / LIST AVAILABLE GRAPHS | Loaded graphs / graphs on disk |
CHANGE NEW / SUBMIT / DELETE / LIST | Create, merge, discard and list changes |
COMMIT | Seal pending writes inside a change |
LOAD COMMIT '<hash>' | Load a past commit (REST API only; the CLI and SDK do it for you) |
MERGE_DATAPARTS | Compact the graph’s DataParts into one (renumbers nodes) |
CREATE VECTOR INDEX <name> WITH DIMENSION <n> METRIC <COSINE|EUCLID> | Create a vector index |
SHOW VECTOR INDEXES / DELETE VECTOR INDEX <name> | List / delete vector indexes |
INSTALL <ext> / SHOW EXTENSIONS / SHOW PROCEDURES | Load a procedure extension / list extensions / list procedures |
EXPLAIN <query> | Show the query plan without running it |

