Skip to content
gr

Reading graphs

MATCH patterns, WHERE filters, RETURN, ORDER BY, LIMIT, OPTIONAL MATCH, and WITH.

MATCH

MATCH is the core of every read query. It describes a graph pattern and the engine returns every subgraph that fits it.

MATCH (n:Person)
RETURN n.name, n.age

A node pattern (n:Person) matches every node with the label Person and binds it to the variable n. A relationship pattern connects two node patterns:

MATCH (a:Person)-[:KNOWS]->(b:Person)
RETURN a.name, b.name

The arrow direction matters. -[:KNOWS]-> follows the relationship in its stored direction. <-[:KNOWS]- follows it backwards. -[:KNOWS]- (no arrow) matches either direction.

To match any relationship type:

MATCH (a)-[r]->(b)
RETURN type(r), a.name, b.name

Node and relationship properties in patterns

Inline property predicates narrow the pattern:

MATCH (p:Person {name:"Alice", age:30})
RETURN p

This is equivalent to a WHERE clause:

MATCH (p:Person)
WHERE p.name = "Alice" AND p.age = 30
RETURN p

Use the inline form for constant look-ups and WHERE for expressions.

WHERE

WHERE accepts any boolean expression:

MATCH (p:Person)
WHERE p.age > 25 AND p.name STARTS WITH "A"
RETURN p.name

String predicates: STARTS WITH, ENDS WITH, CONTAINS.

Null checks: IS NULL, IS NOT NULL.

Negation: NOT.

List membership: n.name IN ["Alice", "Bob"].

Label check: n:Person.

RETURN

RETURN projects the result. Use aliases to rename columns:

MATCH (p:Person)
RETURN p.name AS name, p.age AS age

RETURN * returns all bound variables.

RETURN DISTINCT removes duplicate rows:

MATCH (p:Person)-[:KNOWS]->(friend:Person)
RETURN DISTINCT friend.name

ORDER BY, SKIP, LIMIT

MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC
SKIP 10
LIMIT 5

ORDER BY accepts multiple columns and ASC/DESC per column. SKIP and LIMIT accept integer literals or parameters.

OPTIONAL MATCH

OPTIONAL MATCH is a LEFT OUTER JOIN. If the pattern does not match, the variables from it are null instead of the row being dropped:

MATCH (p:Person)
OPTIONAL MATCH (p)-[:HAS_EMAIL]->(e:Email)
RETURN p.name, e.address

Persons without an email still appear; e.address is null for them.

Multiple MATCH clauses

Multiple MATCH clauses in the same query combine like a cross product filtered by shared variables:

MATCH (a:Person {name:"Alice"})
MATCH (b:Person {name:"Bob"})
MATCH (a)-[:KNOWS]->(b)
RETURN a.name, b.name

WITH

WITH is a pipeline separator. It ends one clause, projects intermediate results, and feeds them into the next:

MATCH (p:Person)-[:KNOWS]->(friend:Person)
WITH p, count(friend) AS friendCount
WHERE friendCount > 3
RETURN p.name, friendCount
ORDER BY friendCount DESC

WITH is required whenever you aggregate before filtering on the aggregated value.

UNWIND

UNWIND turns a list into rows:

WITH ["Alice", "Bob", "Carol"] AS names
UNWIND names AS name
MATCH (p:Person {name: name})
RETURN p

This is the idiomatic way to look up a batch of known values.

Parameters

Always use parameters for user-supplied values:

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

Pass the parameter map from Go:

res, err := db.Query(ctx,
    `MATCH (p:Person {name: $name}) RETURN p.age`,
    map[string]any{"name": "Alice"},
)

Parameters prevent injection and let the planner cache the query plan.

Running a read in Go

res, err := db.Query(ctx, `
    MATCH (a:Person)-[:KNOWS]->(b:Person)
    WHERE a.name = $name
    RETURN b.name AS friend, b.age AS age
    ORDER BY b.age
`, map[string]any{"name": "Alice"})
if err != nil {
    return err
}
defer res.Close()

for res.Next() {
    rec := res.Record()
    fmt.Printf("%v (age %v)\n", rec.Get("friend"), rec.Get("age"))
}
return res.Err()

db.Query rejects queries that write. Use db.Run or db.Exec for write queries.