Structure isolated units of work with CALL { } subqueries and explicit imports.
Choose between UNION and UNION ALL based on deduplication needs.
Express pattern existence with EXISTS { } subqueries.
Transform lists with list comprehensions without mandatory UNWIND/COLLECT round-trips.
Store and query temporal values with Neo4j temporal types and functions.
Call APOC procedures only when needed, with clear dependency and security caveats.
Compose advanced patterns against the Movie graph for analytics and application features.
Core concepts
CALL subqueries and scope
As queries grow, you need nested units of work: per-movie aggregations inside a larger
report, or a write subquery that must not leak intermediate variables. The
CALL { ... } subquery form executes an inner query and
returns its projected rows to the outer scope. This is more structured than stacking many
WITH clauses alone, especially when each outer row should
drive an independent inner computation.
Scope and import rules are version-sensitive. In modern Cypher, outer variables are not
freely visible inside the subquery unless you import them—commonly via a scope clause such
as CALL (movie) { ... } or the documented import form for
your Neo4j version. Always check the Cypher Manual’s “CALL subquery” page for the exact
import syntax on your server. Treating imports as explicit documentation of data flow
prevents subtle bugs when refactoring.
Subqueries can be read-only or contain writes. Combining writes with union-like fan-out
requires care around transactional boundaries; prefer clear transaction functions in the
driver (Lesson 10) for multi-step application workflows.
CYPHER
MATCH (m:Movie)
WHERE m.released >= 2000CALL (m) {
MATCH (m)<-[:ACTED_IN]-(p:Person)
WITH p
ORDER BY p.name
RETURNcount(p) AS castSize,
collect(p.name)[0..3] AS sampleCast
}
RETURN m.title AS title, castSize, sampleCast
ORDER BY castSize DESC
If your Neo4j version does not yet support the parenthesized import form, use the documented
alternative (historically an importing WITH at the start of
the subquery). The educational point is the same: make outer-to-inner data flow explicit.
UNION, UNION ALL, and EXISTS
UNION concatenates compatible result columns from multiple
query branches and removes duplicate rows.
UNION ALL concatenates without deduplication and is cheaper
when duplicates are impossible or acceptable. Column names and types must align across
branches—alias carefully.
Existence predicates answer “is there any match?” without returning the matched data.
EXISTS { MATCH pattern } (or the pattern form documented
for your version) is true when the inner pattern succeeds under current bindings. Prefer
EXISTS over collecting and checking list size when you only need a boolean, because the
engine can stop at the first match.
Classic co-actor or “movies with reviews” filters become clearer as EXISTS predicates than
as optional matches with null checks, especially when the pattern is multi-hop.
List comprehensions, temporals, and APOC
List comprehensions compactly project and filter lists:
[name IN names WHERE name STARTS WITH 'T' | toUpper(name)].
Combined with collect, they shape API payloads without extra
UNWIND stages. They do not replace graph pattern matching; they refine list values already
in scope.
Neo4j temporal types—Date, DateTime,
LocalDateTime, Time,
Duration—support precise time modeling beyond integer years.
Construct values with functions such as date('1999-03-31')
or datetime() for “now.” The Movie sample often stores
release years as integers; when you evolve a model, migrate deliberately and index the
temporal properties you filter on. Temporal arithmetic uses durations, for example
date() + duration('P7D').
APOC (Awesome Procedures on Cypher) is a widely used plugin providing
utilities for data integration, refactoring, and advanced helpers. It is
not part of core Cypher: clusters may disable it, versions may diverge, and
procedure allow-lists / security configs restrict what is callable. Prefer core Cypher for
portable application logic. When you need APOC, pin versions, document the dependency, and
gate features when procedures are missing.
CALL subqueries nest per-row computation; imports define which outer bindings enter the inner scope.
Worked examples
Dataset note: Expected results use the classic Neo4j Movie graph (37 movies,
133 people) and can differ if the data changes.
01
UNION ALL of actors and directors for a movie
Combine people connected via ACTED_IN or DIRECTED to “The Matrix”, labeling their role. UNION ALL
keeps a person who both acted and directed as two rows if that ever occurs.
CYPHER
MATCH (m:Movie {title: 'The Matrix'})<-[:ACTED_IN]-(p:Person)
RETURN p.name AS name, 'actor'AS role
UNION ALLMATCH (m:Movie {title: 'The Matrix'})<-[:DIRECTED]-(p:Person)
RETURN p.name AS name, 'director'AS role
ORDER BY role, name
Expected result
name
role
Carrie-Anne Moss
actor
Emil Eifrem
actor
Hugo Weaving
actor
Keanu Reeves
actor
Laurence Fishburne
actor
Lana Wachowski
director
Lilly Wachowski
director
02
EXISTS filter for reviewed movies
Return post-1995 movies that have at least one REVIEWED relationship, without aggregating reviews.
CYPHER
MATCH (m:Movie)
WHERE m.released > 1995AND EXISTS {
MATCH (m)<-[:REVIEWED]-(:Person)
}
RETURN m.title AS title, m.released AS released
ORDER BY released, title
Expected result
title
released
The Birdcage
1996
Jerry Maguire
2000
The Replacements
2000
The Da Vinci Code
2006
Cloud Atlas
2012
If your local Movie sample has a different review set, the title list may vary—confirm with
MATCH (m)<-[:REVIEWED]-() RETURN m.title, m.released and keep the
EXISTS shape the same.
03
List comprehension on a collected cast
Cloud Atlas has four actors in the classic graph: Halle Berry, Hugo Weaving, Jim Broadbent, and
Tom Hanks. Collect names in alphabetical order, then keep only names longer than 10 characters via
list comprehension (size(n) > 10 drops “Tom Hanks”).
CYPHER
MATCH (m:Movie {title: 'Cloud Atlas'})<-[:ACTED_IN]-(p:Person)
WITH m, p
ORDER BY p.name
WITH m, collect(p.name) AS names
RETURN m.title AS title,
[n IN names WHERE size(n) > 10 | n] AS longNames,
size(names) AS castSize
Expected result
title
longNames
castSize
Cloud Atlas
["Halle Berry", "Hugo Weaving", "Jim Broadbent"]
4
04
Temporal construction alongside integer years
Show how to attach a Date value derived from an integer release year for reporting, without
permanently migrating the property in this example.
CYPHER
MATCH (m:Movie {title: 'The Matrix'})
WITH m, date({year: m.released, month: 1, day: 1}) AS approxRelease
RETURN m.title AS title,
m.released AS yearInt,
approxRelease,
approxRelease + duration('P1Y') AS oneYearLater
Expected result
title
yearInt
approxRelease
oneYearLater
The Matrix
1999
1999-01-01
2000-01-01
Pitfalls
Forgetting subquery imports
Inner queries that reference unbound outer variables fail or behave unexpectedly. Import
deliberately and keep subquery interfaces small.
Using UNION when UNION ALL is enough
Deduplication has a cost. If branches are disjoint by construction, UNION ALL is clearer and faster.
Hard-depending on APOC in portable apps
Aura configurations, locked-down enterprises, and version skew can break APOC calls. Isolate
optional features and detect missing procedures gracefully.
Mixing temporal types carelessly
Comparing DateTime to Date or to epoch integers without conversion yields errors or wrong filters.
Normalize types at write time.
Exercises
1. EXISTS for co-actors
Return people who have at least one co-actor via shared movies (EXISTS pattern), limited to 10 names.
STARTER
MATCH (p:Person)
WHERE EXISTS { /* co-actor pattern */ }
RETURN p.name
LIMIT10
2. UNION movie titles by relationship
UNION titles of movies Tom Hanks acted in with titles he directed (if any). Use UNION to deduplicate.
STARTER
MATCH (p:Person {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie)
RETURN m.title AS title
UNIONMATCH (p:Person {name: 'Tom Hanks'})-[:DIRECTED]->(m:Movie)
RETURN m.title AS title
3. List comprehension cleanup
Collect actor names for The Matrix and return a list comprehension that uppercases each name.
STARTER
MATCH (m:Movie {title: 'The Matrix'})<-[:ACTED_IN]-(p:Person)
WITH p
ORDER BY p.name
WITHcollect(p.name) AS names
RETURN [n IN names | /* toUpper(n) */ ]
4. Optional APOC discovery
Without requiring APOC to be installed, write a note query that lists how you would call a procedure if present—and a pure Cypher fallback that counts movies.
STARTER
// Fallback always works:MATCH (m:Movie)
RETURNcount(m) AS movies
// APOC example (may fail if plugin missing):// CALL apoc.meta.stats() YIELD nodeCount RETURN nodeCount
Lesson quiz
Five questions. Submit once to see your score and the full answer key.