Advanced Cypher querying
|
This section documents the current Neo4j This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Neo4j iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Beyond a single MATCH / RETURN, Cypher queries are built by chaining intermediate result sets and by
traversing paths whose length or endpoints are not known in advance. This page assumes the
Cypher fundamentals page’s :Person / :Movie /
ACTED_IN domain and builds on it.
WITH for query chaining
WITH passes a projected, optionally aggregated or filtered, result set from one part of a query into the
next — the same variables an earlier MATCH bound are not automatically visible after a WITH unless they
are re-listed in it.
// Actors with 3+ movies, then the titles of those movies
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WITH p, count(m) AS movieCount
WHERE movieCount >= 3
MATCH (p)-[:ACTED_IN]->(m:Movie)
RETURN p.name, movieCount, collect(m.title) AS titles
ORDER BY movieCount DESC;
WITH also carries pagination between stages, ordering results before a downstream MATCH narrows them
further:
MATCH (p:Person)-[:ACTED_IN]->(:Movie)
WITH p, count(*) AS credits
ORDER BY credits DESC
LIMIT 5
RETURN p.name, credits;
CALL \{ \} subqueries
A CALL \{ … \} subquery runs an independent unit of work per incoming row (or once, if uncorrelated) and
returns its results into the outer query. It is the modern replacement for most cases that used to need
FOREACH tricks or repeated MATCH clauses, and it is required for post-UNION continuation and for
per-row aggregation that must not collapse the outer rows.
// For every person, the title of their most recent movie -- computed per row
MATCH (p:Person)
CALL (p) {
MATCH (p)-[:ACTED_IN]->(m:Movie)
RETURN m.title AS latestTitle
ORDER BY m.released DESC
LIMIT 1
}
RETURN p.name, latestTitle;
CALL \{ \} IN TRANSACTIONS runs a subquery in separate, batched transactions — used for large writes rather
than in read-heavy analytical queries like the one above
(CALL \{ \} IN TRANSACTIONS).
UNION and UNION ALL
UNION combines the results of two or more queries that return the same column names, de-duplicating rows;
UNION ALL keeps every row, including duplicates, and is cheaper when duplicates are known not to occur or
do not matter.
MATCH (p:Person)-[:ACTED_IN]->(:Movie {title: 'The Matrix'})
RETURN p.name AS name, 'actor' AS role
UNION ALL
MATCH (p:Person)-[:DIRECTED]->(:Movie {title: 'The Matrix'})
RETURN p.name AS name, 'director' AS role;
List and map comprehensions
A list comprehension filters and/or transforms a list inline, without a separate UNWIND /
collect() round trip. A map comprehension does the same over map entries.
MATCH (p:Person {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie)
RETURN [movie IN collect(m) WHERE movie.released >= 2000 | movie.title] AS recentTitles;
// Map comprehension over a literal map's entries
WITH {matrix: 1999, inception: 2010, dune: 2021} AS releaseYears
RETURN [key IN keys(releaseYears) WHERE releaseYears[key] >= 2010 | key] AS from2010On;
UNWIND
UNWIND expands a list into one row per element — the inverse of collect() — commonly used to turn a
parameter list into rows for matching, or to flatten a list produced earlier in the same query.
UNWIND ['The Matrix', 'Inception', 'Dune'] AS title
MATCH (m:Movie {title: title})<-[:ACTED_IN]-(p:Person)
RETURN title, collect(p.name) AS cast;
OPTIONAL MATCH
OPTIONAL MATCH behaves like an outer join: when the pattern has no match for a given row, its variables
are bound to null instead of dropping the row, so a person with no directing credits still appears once
in the results.
MATCH (p:Person {name: 'Keanu Reeves'})
OPTIONAL MATCH (p)-[:DIRECTED]->(directed:Movie)
RETURN p.name, directed.title;
// directed.title is null if Keanu has never directed a movie
Variable-length paths
A relationship pattern can specify a hop range with min..max instead of a single hop, matching every path
whose length falls in that range. Omitting either bound defaults it ( alone means *0.., unbounded).
// Actors reachable from Tom Hanks within 1 to 3 ACTED_IN hops through shared movies
MATCH (origin:Person {name: 'Tom Hanks'})-[:ACTED_IN*1..3]-(coActor:Person)
WHERE coActor <> origin
RETURN DISTINCT coActor.name
LIMIT 25;
An unbounded or loosely bounded variable-length pattern can traverse a very large part of the graph; always
pair it with a LIMIT, a tight upper bound, or a WHERE predicate that prunes the search
(variable-length relationships).
shortestPath() and allShortestPaths()
shortestPath() returns one shortest path between two already-bound nodes; allShortestPaths() returns
every path tied for shortest. Both require the two endpoint nodes to be matched first and take an upper
bound on hops so the search terminates.
// The shortest chain of shared-movie connections between two actors
MATCH (a:Person {name: 'Tom Hanks'}), (b:Person {name: 'Keanu Reeves'})
MATCH path = shortestPath((a)-[:ACTED_IN*..6]-(b))
RETURN [n IN nodes(path) | coalesce(n.name, n.title)] AS chain, length(path) AS hops;
// Every co-acting connection tied for shortest, not just one of them
MATCH (a:Person {name: 'Tom Hanks'}), (b:Person {name: 'Keanu Reeves'})
MATCH path = allShortestPaths((a)-[:ACTED_IN*..6]-(b))
RETURN [n IN nodes(path) | coalesce(n.name, n.title)] AS chain, length(path) AS hops;
See shortest paths for the
constraints on mixing these functions with other predicates in the same MATCH.
Continue with Cypher fundamentals for the MATCH / WHERE /
RETURN basics these techniques build on.