Lesson 09

Advanced Cypher

Difficulty Intermediate Time

Learning objectives

After this lesson you will be able to:

  1. Structure isolated units of work with CALL { } subqueries and explicit imports.
  2. Choose between UNION and UNION ALL based on deduplication needs.
  3. Express pattern existence with EXISTS { } subqueries.
  4. Transform lists with list comprehensions without mandatory UNWIND/COLLECT round-trips.
  5. Store and query temporal values with Neo4j temporal types and functions.
  6. Call APOC procedures only when needed, with clear dependency and security caveats.
  7. 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 >= 2000
CALL (m) {
  MATCH (m)<-[:ACTED_IN]-(p:Person)
  WITH p
  ORDER BY p.name
  RETURN count(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.

Outer Movie row driving a CALL subquery that aggregates cast and returns sample names
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.

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 ALL
MATCH (m:Movie {title: 'The Matrix'})<-[:DIRECTED]-(p:Person)
RETURN p.name AS name, 'director' AS role
ORDER BY role, name

Expected result

namerole
Carrie-Anne Mossactor
Emil Eifremactor
Hugo Weavingactor
Keanu Reevesactor
Laurence Fishburneactor
Lana Wachowskidirector
Lilly Wachowskidirector

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 > 1995
  AND EXISTS {
    MATCH (m)<-[:REVIEWED]-(:Person)
  }
RETURN m.title AS title, m.released AS released
ORDER BY released, title

Expected result

titlereleased
The Birdcage1996
Jerry Maguire2000
The Replacements2000
The Da Vinci Code2006
Cloud Atlas2012

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.

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

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

Exercises

  1. 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
    LIMIT 10
  2. 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
    UNION
    MATCH (p:Person {name: 'Tom Hanks'})-[:DIRECTED]->(m:Movie)
    RETURN m.title AS title
  3. 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
    WITH collect(p.name) AS names
    RETURN [n IN names | /* toUpper(n) */ ]
  4. 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)
    RETURN count(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.

Loading quiz…

Resources

Video

Watch on YouTube (opens in a new tab).